NestJS 中间件(Middleware)使用指南

NestJS 的 中间件(Middleware) 是一个在路由处理器之前执行的函数。它可以访问请求对象(req)、响应对象(res)和应用程序的请求-响应周期中的 next() 中间件函数。

中间件的作用

中间件函数可以执行以下任务:

  • 执行任何代码
  • 对请求和响应对象进行修改
  • 结束请求-响应周期
  • 调用堆栈中的下一个中间件函数
  • 如果当前中间件函数没有结束请求-响应周期,它必须调用 next() 将控制权传递给下一个中间件函数,否则请求将被挂起

NestJS 中间件的两种实现方式

1. 函数式中间件

函数式中间件是最简单的实现方式,它是一个无状态的函数:

1
2
3
4
5
6
7
// logger.middleware.ts
import { Request, Response, NextFunction } from 'express';

export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
}

2. 类中间件

类中间件需要实现 NestMiddleware 接口,适合有依赖注入需求的场景:

1
2
3
4
5
6
7
8
9
10
11
// logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
}
}

使用中间件

在模块中配置中间件

中间件需要在模块的 configure 方法中配置,模块需要实现 NestModule 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';

@Module({
imports: [],
controllers: [],
providers: [],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('*'); // 应用于所有路由
}
}

限制中间件作用的路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';

@Module({
imports: [],
controllers: [],
providers: [],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('users'); // 仅适用于 /users 路由
}
}

也可以通过 exclude 方法排除某些路径:

1
2
3
4
5
6
7
8
9
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'users/login', method: RequestMethod.POST },
{ path: 'users/register', method: RequestMethod.POST },
)
.forRoutes('users');
}

使用通配符路径匹配

1
2
3
4
5
6
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthMiddleware)
.forRoutes({ path: 'admin/*', method: RequestMethod.ALL });
// 匹配所有 /admin/ 开头的路径
}

指定 HTTP 方法

1
2
3
4
5
6
7
8
import { RequestMethod } from '@nestjs/common';

configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes({ path: 'users', method: RequestMethod.POST });
// 仅拦截 POST /users 请求
}

绑定到指定控制器

更推荐的做法是将中间件绑定到控制器,而不是写死路径字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';
import { UsersController } from './users.controller';

@Module({
controllers: [UsersController],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes(UsersController); // 仅应用于 UsersController 的所有路由
}
}

实战:认证中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// auth.middleware.ts
import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class AuthMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const token = req.headers.authorization?.replace('Bearer ', '');

if (!token) {
throw new UnauthorizedException('缺少认证令牌');
}

try {
// 这里做 token 验证,比如 jwt.verify(token, secret)
const user = { id: 1, username: 'admin' };
(req as any).user = user; // 将用户信息挂载到 request 对象
next();
} catch (error) {
throw new UnauthorizedException('无效的令牌');
}
}
}

实战:请求日志中间件

记录每个接口的请求耗时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// request-timer.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class RequestTimerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const start = Date.now();

res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.originalUrl} ${res.statusCode} - ${duration}ms`);
});

next();
}
}
1
2
3
4
5
6
// app.module.ts
configure(consumer: MiddlewareConsumer) {
consumer
.apply(RequestTimerMiddleware)
.forRoutes('*');
}

实战:CORS 中间件

虽然 NestJS 有内置的 CORS 支持,但手动实现一个可以更灵活地控制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// cors.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class CorsMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');

if (req.method === 'OPTIONS') {
return res.sendStatus(204); // 预检请求直接返回
}

next();
}
}

多个中间件组合

可以同时应用多个中间件,它们会按顺序执行:

1
2
3
4
5
configure(consumer: MiddlewareConsumer) {
consumer
.apply(CorsMiddleware, RequestTimerMiddleware, AuthMiddleware)
.forRoutes('admin');
}

全局中间件

除了在模块中配置,还可以在 main.ts 中直接绑定全局中间件:

1
2
3
4
5
6
7
8
9
10
11
12
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as helmet from 'helmet';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(helmet()); // 全局使用 helmet 安全中间件
app.use(cors());
await app.listen(3000);
}
bootstrap();

函数式 vs 类中间件,如何选择?

对比维度 函数式中间件 类中间件
依赖注入 不支持 支持
复杂度 简单逻辑 复杂逻辑
代码量 较少 稍多
适用场景 纯函数、无状态 需要注入 Logger、ConfigService 等服务

如果你的中间件不需要依赖其他服务,推荐使用函数式中间件,更简洁。如果需要注入 Logger、ConfigService 等服务,则使用类中间件。

中间件 vs 守卫 vs 管道 vs 拦截器

组件 执行时机 主要用途
中间件 最先执行 日志、CORS、请求体解析、静态文件
守卫 中间件之后 鉴权、权限校验
管道 守卫之后 参数校验、类型转换
拦截器 管道之后 响应映射、异常处理

总结

NestJS 中间件是请求处理链的第一道关卡,合理使用中间件可以让代码更加干净和模块化:

  1. 最先执行:中间件在守卫、管道、拦截器之前执行,适合做请求的预处理
  2. 灵活配置:支持路径匹配、方法过滤、控制器级别的绑定
  3. 两种模式:函数式简洁,类中间件支持依赖注入
  4. 可组合:多个中间件可以链式使用,按顺序依次处理

在实际项目中,日志记录、CORS 处理、请求预处理等场景用中间件最合适,而权限验证建议用守卫来实现。