FORMA

中间件 (Middleware)

中间件在路由之前执行,最接近 Express 层。见官方请求生命周期。

中间件是在路由处理器之前执行的函数,可以访问请求和响应对象,并能修改它们或结束请求-响应周期。NestJS 的中间件与 Express/Fastify 的中间件概念一致,但额外支持依赖注入。

一、中间件的特点

特性说明
可注入依赖与 Nest 的 IoC 容器集成,可以注入服务(如 LoggerService
执行顺序位于守卫和管道之前,即先经过中间件,再进入守卫,最后到管道和控制器
与普通中间件一致可以调用 next() 传递给下一个中间件,或者直接发送响应终止流程
局限性无法访问 ExecutionContext,不能像守卫那样获取执行的控制类/方法元数据

执行顺序示意

text
请求 → 中间件 → 守卫 → 管道(验证/转换)→ 控制器 → 拦截器(前置)→ 路由处理器 → 拦截器(后置)→ 响应

二、定义中间件

1. 类式中间件(支持依赖注入)

实现 NestMiddleware 接口的 use 方法。

ts
import { Injectable, NestMiddleware } from "@nestjs/common";
import { Request, Response, NextFunction } from "express";

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  // 可以注入其他服务
  constructor(private readonly logger: LoggerService) {}

  use(req: Request, res: Response, next: NextFunction) {
    console.log(`[${req.method}] ${req.url}`);
    this.logger.log(`Request received`);
    next(); // 必须调用以传递控制权
  }
}

2. 函数式中间件(简单场景)

对于没有依赖的简单中间件,可以使用函数形式。

ts
export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`Request...`);
  next();
}

三、配置中间件

中间件需要在某个模块中通过 MiddlewareConsumer 进行配置,通常在模块的 configure 方法中。

ts
import { Module, NestModule, MiddlewareConsumer } from "@nestjs/common";
import { LoggerMiddleware } from "./logger.middleware";
import { UsersController } from "./users.controller";

@Module({
  controllers: [UsersController],
})
export class UsersModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware) // 应用中间件(可以多个)
      .forRoutes("users"); // 指定路由路径(字符串或路由对象)
    // 或者 forRoutes(UsersController) 应用到控制器所有路由
    // 或者 forRoutes({ path: 'users', method: RequestMethod.GET })
  }
}

1. 路径匹配与排除

  • forRoutes:可接受控制器类、路由字符串或路由对象。
  • exclude:排除某些路径不应用中间件。
ts
consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: "users", method: RequestMethod.POST }, // 排除 POST /users
    { path: "users/(.*)", method: RequestMethod.ALL }, // 排除所有 /users/* 路由
  )
  .forRoutes(UsersController);

2. 应用多个中间件

apply 可以接受逗号分隔的多个中间件。

ts
consumer.apply(cors(), helmet(), LoggerMiddleware).forRoutes("*");

四、全局中间件

如果希望中间件应用于所有路由(如 CORS、Helmet),可以在 main.ts 中直接使用 app.use()

ts
// main.ts
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { logger } from "./common/middleware/logger.middleware";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(logger); // 函数式中间件
  // 或者 app.use(new LoggerMiddleware().use) 但无法注入依赖
  await app.listen(3000);
}
bootstrap();

注意:全局中间件无法通过类形式获得依赖注入(除非使用 APP_MIDDLEWARE 令牌?实际上常规 app.use 不支持 DI),如果需要 DI 的全局中间件,可以创建一个模块实现 MiddlewareConsumer 并配置 * 路径,但这样每个模块都需要导入该模块。更简单的方式是使用全局守卫/拦截器代替。

五、常见用途

用途说明
日志记录记录每个请求的方法、URL、响应时间等
请求预处理解析自定义请求头、注入请求上下文
CORS使用 NestJS 内置 cors() 中间件(通过 app.enableCors()
Helmet设置安全相关的 HTTP 头:app.use(helmet())
压缩启用响应压缩(如 compression 中间件)
限流实现简单的请求频率限制(如 express-rate-limit

CORS 启用示例

ts
// main.ts
const app = await NestFactory.create(AppModule);
app.enableCors(); // 也可以传入配置对象

六、中间件与守卫、管道的区别

组件执行时机可访问内容主要用途
中间件最早(路由匹配后,守卫之前)Request, Response日志、预处理、跨域等
守卫中间件之后,管道之前ExecutionContext,可获取元数据鉴权、授权
管道守卫之后,控制器之前参数(如 @Body 等)验证、转换

总结

  • 类式中间件通过 @Injectable() 支持依赖注入;函数式中间件更简单,适用于无依赖场景。
  • 在模块中实现 NestModule 接口,通过 MiddlewareConsumer 配置中间件的应用路由、排除路径。
  • 全局中间件使用 app.use() 注册,但无法注入依赖。
  • 常用场景:日志、安全头(Helmet)、CORS、请求预处理等。

掌握中间件,可以在请求生命周期的早期阶段进行统一处理,而不侵入业务逻辑。

参考文献

以下链接在编写时均可正常访问:

资料说明
NestJS 文档官方
Middleware本章主题
Request lifecycle执行顺序

Series

new

14 / 19