Skip to content

什么是中间件?🤔 ​

中间件(Middleware)是在路由处理程序之前调用的函数。中间件函数可以访问请求和响应对象,以及应用程序的请求-响应周期中的 next() 中间件函数。

Express vs NestJS 对比 🆚 ​

Express 写法:

javascript
const express = require('express');
const app = express();

// 日志中间件
const logger = (req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
};

// 认证中间件
const auth = (req, res, next) => {
  if (!req.headers.authorization) {
    return res.status(401).json({ message: '未授权' });
  }
  next();
};

// 使用中间件
app.use(logger);
app.use('/protected', auth);

NestJS 写法:

typescript
// logger.middleware.ts
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log(`${req.method} ${req.url}`);
    next();
  }
}

// auth.middleware.ts
@Injectable()
export class AuthMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    if (!req.headers.authorization) {
      throw new UnauthorizedException();
    }
    next();
  }
}

// app.module.ts
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('*')
      .apply(AuthMiddleware)
      .forRoutes('protected');
  }
}

中间件的类型 🎯 ​

1. 类中间件 ​

typescript
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  private readonly logger = new Logger('HTTP');

  use(req: Request, res: Response, next: NextFunction) {
    const { ip, method, originalUrl } = req;
    const userAgent = req.get('user-agent') || '';

    res.on('finish', () => {
      const { statusCode } = res;
      this.logger.log(
        `${method} ${originalUrl} ${statusCode} - ${userAgent} ${ip}`
      );
    });

    next();
  }
}

2. 函数中间件 🚀 ​

typescript
// 简单的函数中间件
export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`${req.method} ${req.url}`);
  next();
}

// 在模块中使用
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(logger)
      .forRoutes('*');
  }
}

中间件的应用方式 📝 ​

1. 全局中间件 ​

typescript
// main.ts
const app = await NestFactory.create(AppModule);
app.use(logger);  // 👈 全局应用中间件
await app.listen(3000);

2. 模块级中间件 🔧 ​

typescript
@Module({
  imports: [UsersModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware, AuthMiddleware)  // 👈 可以应用多个中间件
      .exclude(  // 👈 排除特定路由
        { path: 'users', method: RequestMethod.GET },
        'users/(.*)',
      )
      .forRoutes(  // 👈 指定应用的路由
        { path: 'users', method: RequestMethod.POST },
        { path: 'auth/*', method: RequestMethod.ALL }
      );
  }
}

异步中间件 ⚡ ​

typescript
@Injectable()
export class AsyncMiddleware implements NestMiddleware {
  constructor(private readonly configService: ConfigService) {}

  async use(req: Request, res: Response, next: NextFunction) {
    const apiKey = req.headers['x-api-key'];
    const validApiKey = await this.configService.get('API_KEY');
    
    if (apiKey !== validApiKey) {
      throw new UnauthorizedException('Invalid API Key');
    }
    
    next();
  }
}

实战示例 💡 ​

让我们创建一个完整的中间件系统:

typescript
// middlewares/request-logger.middleware.ts
@Injectable()
export class RequestLoggerMiddleware implements NestMiddleware {
  private readonly logger = new Logger('HTTP');

  use(req: Request, res: Response, next: NextFunction) {
    const startTime = Date.now();
    const { ip, method, originalUrl } = req;
    const userAgent = req.get('user-agent') || '';

    res.on('finish', () => {
      const { statusCode } = res;
      const responseTime = Date.now() - startTime;

      this.logger.log(
        `${method} ${originalUrl} ${statusCode} ${responseTime}ms - ${userAgent} ${ip}`
      );
    });

    next();
  }
}

// middlewares/rate-limiter.middleware.ts
@Injectable()
export class RateLimiterMiddleware implements NestMiddleware {
  private readonly store = new Map<string, number[]>();
  
  use(req: Request, res: Response, next: NextFunction) {
    const ip = req.ip;
    const now = Date.now();
    const windowMs = 60000; // 1分钟
    const max = 100; // 最大请求数

    const requests = this.store.get(ip) || [];
    const windowStart = now - windowMs;

    // 清理旧的请求记录
    const recentRequests = requests.filter(time => time > windowStart);
    
    if (recentRequests.length >= max) {
      throw new HttpException('Too Many Requests', 429);
    }

    recentRequests.push(now);
    this.store.set(ip, recentRequests);

    next();
  }
}

// app.module.ts
@Module({
  imports: [UsersModule, AuthModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(RequestLoggerMiddleware, RateLimiterMiddleware)
      .exclude(
        { path: 'health', method: RequestMethod.GET }
      )
      .forRoutes('*');
  }
}

最佳实践 ✨ ​

1. 保持中间件简单 ​

typescript
// ❌ 不好的做法:中间件做太多事情
@Injectable()
export class ComplexMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    // 验证请求
    // 修改请求体
    // 查询数据库
    // 发送邮件
    // ...
    next();
  }
}

// ✅ 好的做法:每个中间件只负责一个功能
@Injectable()
export class AuthMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    // 只处理认证逻辑
    next();
  }
}

2. 错误处理 ​

typescript
@Injectable()
export class ErrorHandlerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    try {
      next();
    } catch (error) {
      // 根据错误类型返回适当的响应
      if (error instanceof UnauthorizedException) {
        res.status(401).json({ message: '未授权' });
      } else {
        res.status(500).json({ message: '服务器错误' });
      }
    }
  }
}

3. 使用依赖注入 ​

typescript
@Injectable()
export class CacheMiddleware implements NestMiddleware {
  constructor(
    private readonly cacheService: CacheService,
    private readonly configService: ConfigService,
  ) {}

  async use(req: Request, res: Response, next: NextFunction) {
    const cacheKey = req.originalUrl;
    const ttl = this.configService.get('CACHE_TTL');
    
    const cachedData = await this.cacheService.get(cacheKey);
    if (cachedData) {
      return res.json(cachedData);
    }
    
    next();
  }
}

中间件执行顺序 🔄 ​

中间件的执行顺序很重要:

typescript
consumer
  .apply(Middleware1, Middleware2, Middleware3)
  .forRoutes('*');

// 执行顺序:
// 1. Middleware1
// 2. Middleware2
// 3. Middleware3
// 4. Route Handler
// 5. Response

总结 📝 ​

通过这篇文章,我们学习了:

  1. 中间件的基本概念
  2. 不同类型的中间件
  3. 中间件的应用方式
  4. 异步中间件的实现
  5. 中间件的最佳实践

中间件是处理横切关注点(如日志、认证、限流等)的理想选择,合理使用中间件可以让我们的应用更加健壮和可维护。

下一篇文章,我们将探讨 NestJS 的管道(Pipe)系统。

相关资源 📚 ​