'# 【NestJS】中间件
一、背景与问题
在现代 Web 开发中,中间件(Middleware)是构建高效、可维护系统的核心组件。NestJS 作为基于 Node.js 的分层架构框架,其中间件系统在功能上继承了 Express 的核心机制,同时通过装饰器和依赖注入等特性提供了更优雅的使用体验。
中间件在 NestJS 中扮演着多重角色:
- 请求处理管道:在请求到达控制器之前进行预处理
- 异常处理:统一处理运行时错误
- 日志记录:集中管理请求日志
- 身份验证:统一校验用户权限
- 性能监控:统计接口响应时间
典型的使用场景包括:身份验证中间件、日志记录中间件、错误处理中间件、请求解析中间件等。但如果不理解其底层原理,容易出现诸如请求阻塞、异常泄露、性能瓶颈等问题。
二、基本原理
1. 中间件的执行流程
NestJS 中间件的执行顺序遵循洋葱模型,请求会依次经过每个中间件的 handle 方法,直到遇到 next() 调用,最终到达控制器处理函数。
// 中间件执行流程
function middleware1(req, res, next) {
console.log('Middleware 1');
next();
}
function middleware2(req, res, next) {
console.log('Middleware 2');
next();
}
// 请求依次经过 middleware1 -> middleware2 -> 控制器2. 中间件的作用域
NestJS 中间件分为三类:
- 全局中间件:通过
use方法注册,适用于所有路由 - 路由中间件:通过
use方法绑定到特定路由 - 控制器中间件:通过
@Use装饰器绑定到控制器方法
3. 异步处理机制
NestJS 中间件支持异步处理,通过 Promise 或 async/await 实现非阻塞处理:
async function asyncMiddleware(req, res, next) {
console.log('Async middleware');
await new Promise(resolve => setTimeout(resolve, 100));
next();
}4. 异常处理机制
当中间件抛出异常时,NestJS 会自动触发全局异常处理程序,但需要显式注册错误处理中间件:
function errorMiddleware(err, req, res, next) {
console.error(err.stack);
res.status(500).json({ message: 'Internal server error' });
}三、环境准备
npm install @nestjs/common @nestjs/core
npm install --save-dev @types/express项目结构建议:
src/
├── middleware/
│ ├── auth.middleware.ts
│ ├── logger.middleware.ts
│ └── error.middleware.ts
├── controllers/
│ └── hello.controller.ts
├── main.ts
└── app.module.ts四、核心实现
1. 基础中间件实现
// src/middleware/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(`[Logger] ${req.method} ${req.url}`);
next();
}
}关键点解释:
- 实现
NestMiddleware接口 - 使用
@Injectable()装饰器 - 参数类型需显式声明
next()必须调用以继续处理流程
2. 异步中间件实现
// src/middleware/async.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class AsyncMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
setTimeout(() => {
console.log('Async middleware executed');
next();
}, 100);
}
}3. 错误处理中间件
// src/middleware/error.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class ErrorMiddleware implements NestMiddleware {
use(err: any, req: Request, res: Response, next: NextFunction) {
console.error('Error occurred:', err.stack);
res.status(500).json({
message: 'Internal server error',
error: err.message,
});
}
}五、完整案例
用户认证系统实现
1. 定义认证中间件
// src/middleware/auth.middleware.ts
import { Injectable, NestMiddleware } 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['x-token'];
if (!token || token !== 'secret-token') {
res.status(401).json({ message: 'Unauthorized' });
return;
}
next();
}
}2. 控制器实现
// src/controllers/hello.controller.ts
import { Controller, Get, UseMiddleware } from '@nestjs/common';
import { AuthMiddleware } from '../middleware/auth.middleware';
@Controller('api')
@UseMiddleware(AuthMiddleware)
export class HelloController {
@Get()
getHello(): string {
return 'Hello, authorized user!';
}
}3. 主程序配置
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { LoggerMiddleware } from './middleware/logger.middleware';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 注册全局中间件
app.use(LoggerMiddleware);
await app.listen(3000);
}
bootstrap();六、源码解析
1. 中间件注册机制
在 NestFactory.create() 方法中,会创建 HttpServer 实例,其中包含 use() 方法:
// @nestjs/core/http/http-server.ts
class HttpServer {
use(middleware: NestMiddleware) {
this.middlewares.push(middleware);
return this;
}
}2. 中间件调用流程
当请求到达时,HttpServer 会遍历所有注册的中间件,依次调用 use() 方法:
// @nestjs/core/http/http-server.ts
handleRequest(req: Request, res: Response) {
this.middlewares.forEach(middleware => {
middleware.use(req, res, () => {
// 处理后续中间件
});
});
}七、进阶使用
1. 中间件组合使用
// src/middleware/combined.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class CombinedMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log('Combined middleware');
this.logMiddleware(req, res, next);
}
private logMiddleware(req: Request, res: Response, next: NextFunction) {
console.log(`Request: ${req.method} ${req.url}`);
next();
}
}2. 响应拦截器与中间件的差异
| 特性 | 中间件 | 响应拦截器 |
|---|---|---|
| 作用域 | 路由/全局 | 路由/全局 |
| 执行顺序 | 洋葱模型 | 洋葱模型 |
| 处理对象 | 请求/响应 | 响应 |
| 适用场景 | 预处理、日志 | 响应格式化、压缩 |
八、性能与工程实践
1. 性能优化策略
- 避免同步阻塞:使用
async/await替代setTimeout - 限制中间件数量:减少不必要的中间件注册
- 异步处理分离:将耗时操作移到单独的 worker 进程
- 缓存中间件结果:对频繁访问的接口使用缓存
2. 安全风险分析
| 风险类型 | 描述 | 解决方案 |
|---|---|---|
| 异常泄露 | 未捕获的异常可能导致敏感信息暴露 | 使用 try/catch 包裹中间件逻辑 |
| 未授权访问 | 身份验证中间件实现不完善 | 使用 JWT 或 OAuth2 标准协议 |
| 拒绝服务 | 中间件逻辑存在无限循环 | 增加超时机制和请求限制 |
3. 中间件设计原则
- 单一职责原则:每个中间件只处理一个功能
- 可测试性:使用 mock 对象进行单元测试
- 可配置性:通过配置文件控制中间件行为
- 可扩展性:支持动态注册和热更新
九、常见问题与踩坑
1. 常见错误示例
// 错误示例:忘记调用 next()
function wrongMiddleware(req, res, next) {
console.log('Wrong middleware');
// 忘记调用 next()
}错误原因:请求会卡在该中间件,导致服务器无响应
解决方案:确保每个中间件都调用 next() 或处理完请求后调用
2. 常见问题分析
| 问题 | 现象 | 解决方案 |
|---|---|---|
| 中间件未生效 | 控制器方法被调用但中间件未执行 | 检查中间件注册顺序 |
| 异常未处理 | 未捕获的异常导致服务器崩溃 | 添加全局异常处理中间件 |
| 性能瓶颈 | 中间件处理耗时过长 | 优化逻辑或使用异步处理 |
3. 中间件与路由的优先级
// 错误示例:中间件和路由绑定顺序错误
app.use('/api', AuthMiddleware);
app.get('/api/data', (req, res) => { ... });问题:中间件未正确绑定到路由
正确做法:使用 @UseMiddleware 装饰器绑定到控制器方法
十、最佳实践
1. 推荐使用场景
- 统一的请求日志记录
- 身份验证和授权
- 请求格式校验(如 JSON 解析)
- 响应格式统一(如返回标准 JSON 结构)
- 性能监控(如记录接口耗时)
2. 不推荐使用场景
- 复杂的业务逻辑处理(应使用服务层)
- 需要深度依赖上下文的逻辑(应使用装饰器或依赖注入)
- 需要共享状态的逻辑(应使用全局变量或服务)
3. 代码组织建议
- 按功能模块划分中间件文件
- 使用
@UseMiddleware装饰器绑定到控制器 - 对关键中间件添加单元测试
- 对敏感中间件添加日志记录和监控
十一、总结
NestJS 中间件系统是构建高性能、可维护 Web 应用的核心组件。通过深入理解其工作原理,开发者可以更有效地利用中间件处理请求预处理、异常处理、身份验证等场景。在实际项目中,需要根据业务需求选择合适的中间件实现方式,同时注意避免常见的性能和安全问题。
建议在以下场景使用中间件:
- 需要统一处理的请求/响应逻辑
- 需要跨多个控制器的公共功能
- 需要异步处理的业务逻辑
避免在以下场景使用中间件:
- 涉及复杂业务逻辑的处理
- 需要深度上下文依赖的逻辑
- 需要共享状态的逻辑
通过合理使用中间件,可以显著提升代码的可维护性、可测试性和可扩展性,同时避免常见的性能陷阱和安全风险。