NestJS 的 中间件(Middleware) 是一个在路由处理器之前执行的函数。它可以访问请求对象(req)、响应对象(res)和应用程序的请求-响应周期中的 next() 中间件函数。
中间件的作用
中间件函数可以执行以下任务:
- 执行任何代码
- 对请求和响应对象进行修改
- 结束请求-响应周期
- 调用堆栈中的下一个中间件函数
- 如果当前中间件函数没有结束请求-响应周期,它必须调用
next() 将控制权传递给下一个中间件函数,否则请求将被挂起
NestJS 中间件的两种实现方式
1. 函数式中间件
函数式中间件是最简单的实现方式,它是一个无状态的函数:
1 2 3 4 5 6 7
| 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
| 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
| 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'); } }
|
也可以通过 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 }); }
|
指定 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 }); }
|
绑定到指定控制器
更推荐的做法是将中间件绑定到控制器,而不是写死路径字符串:
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'; import { UsersController } from './users.controller';
@Module({ controllers: [UsersController], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes(UsersController); } }
|
实战:认证中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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 { const user = { id: 1, username: 'admin' }; (req as any).user = user; next(); } catch (error) { throw new UnauthorizedException('无效的令牌'); } } }
|
实战:请求日志中间件
记录每个接口的请求耗时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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
| 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
| 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
| 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()); app.use(cors()); await app.listen(3000); } bootstrap();
|
函数式 vs 类中间件,如何选择?
| 对比维度 |
函数式中间件 |
类中间件 |
| 依赖注入 |
不支持 |
支持 |
| 复杂度 |
简单逻辑 |
复杂逻辑 |
| 代码量 |
较少 |
稍多 |
| 适用场景 |
纯函数、无状态 |
需要注入 Logger、ConfigService 等服务 |
如果你的中间件不需要依赖其他服务,推荐使用函数式中间件,更简洁。如果需要注入 Logger、ConfigService 等服务,则使用类中间件。
中间件 vs 守卫 vs 管道 vs 拦截器
| 组件 |
执行时机 |
主要用途 |
| 中间件 |
最先执行 |
日志、CORS、请求体解析、静态文件 |
| 守卫 |
中间件之后 |
鉴权、权限校验 |
| 管道 |
守卫之后 |
参数校验、类型转换 |
| 拦截器 |
管道之后 |
响应映射、异常处理 |
总结
NestJS 中间件是请求处理链的第一道关卡,合理使用中间件可以让代码更加干净和模块化:
- 最先执行:中间件在守卫、管道、拦截器之前执行,适合做请求的预处理
- 灵活配置:支持路径匹配、方法过滤、控制器级别的绑定
- 两种模式:函数式简洁,类中间件支持依赖注入
- 可组合:多个中间件可以链式使用,按顺序依次处理
在实际项目中,日志记录、CORS 处理、请求预处理等场景用中间件最合适,而权限验证建议用守卫来实现。