第 6 章:中间件
学习目标
- 理解中间件的执行时机
- 写一个 Logger 中间件
- 区分函数式和类式中间件
- 避开 3 个中间件坑
一、什么是中间件
中间件在路由处理之前执行,可以:
- 改 req/res
- 终止请求(直接
res.send) - 调
next()进入下一环节
执行顺序:请求 → 中间件链 → Guard → Pipe → Controller → Interceptor → 异常过滤器。
二、函数式中间件(简单场景)
typescript
// logger.middleware.ts
import { Request, Response, NextFunction } from 'express';
export function logger(req: Request, res: Response, next: NextFunction) {
const start = Date.now();
res.on('finish', () => { // 响应结束时打日志
const ms = Date.now() - start;
console.log(`${req.method} ${req.url} ${res.statusCode} ${ms}ms`);
});
next(); // ⚠️ 必须调,否则卡住
}挂载:
typescript
// app.module.ts
import { MiddlewareConsumer, NestModule } from '@nestjs/common';
import { logger } from './logger.middleware';
@Module({})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(logger).forRoutes('*'); // 全部路由
}
}三、类式中间件(支持注入)
typescript
// auth.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { UsersService } from './users.service';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
constructor(private readonly users: UsersService) {} // 可以注入!
use(req: Request, res: Response, next: NextFunction) {
const token = req.headers['authorization'];
if (!token) { res.status(401).send('no token'); return; }
req.user = this.users.findByToken(token);
next();
}
}typescript
// app.module.ts
@Module({
providers: [UsersService],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthMiddleware).forRoutes('users'); // 只对 /users 生效
}
}四、forRoutes 的写法
typescript
consumer.apply(AuthMiddleware)
.forRoutes('users'); // 字符串路径
consumer.apply(AuthMiddleware)
.forRoutes({ path: 'users', method: RequestMethod.GET }); // 只 GET
consumer.apply(AuthMiddleware)
.forRoutes(UsersController); // 整个 Controller
consumer.apply(AuthMiddleware)
.forRoutes('*'); // 全部五、中间件顺序
apply() 顺序就是执行顺序,先注册先跑。
typescript
consumer.apply(A, B, C).forRoutes('*');
// 请求 → A → B → C → Controller⚠️ 坑 1:
AuthMiddleware在LoggerMiddleware之前 → 401 不会被记录 → 把 logger 放最前。
六、终止请求 vs 抛异常
typescript
// 终止
res.status(401).send({ code: 401, msg: 'no token' });
// 抛异常(推荐走全局过滤器)
throw new UnauthorizedException('no token');中间件里不能用 @Catch 装饰器,异常请直接抛(过滤器会接住)。
七、CORS、Helmet、Body Parser
NestJS 在 main.ts 里用 Express 风格中间件:
typescript
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({ // CORS
origin: 'http://localhost:5173',
credentials: true,
});
app.use(helmet()); // 安全头
app.useGlobalGuards(new RolesGuard()); // 全局守卫
await app.listen(3000);
}
bootstrap();⚠️ 坑 2:
enableCors和前端withCredentials: true必须同时开,否则 Cookie 不带上。
八、全局中间件
typescript
const app = await NestFactory.create(AppModule);
app.use(morgan('dev')); // 全局,不走 NestModule适合第三方中间件(无法用 NestModule 注册的)。
九、实战:请求 ID 中间件
typescript
import { v4 as uuid } from 'uuid';
@Injectable()
export class RequestIdMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const id = req.headers['x-request-id'] ?? uuid();
res.setHeader('X-Request-Id', id); // 响应带回去
(req as any).id = id; // 给 Controller 用
next();
}
}⚠️ 坑 3:中间件里改 req 加字段,需要
(req as any).x = ...,没有标准类型。
十、本章小结
| 类型 | 适用 |
|---|---|
| 函数式 | 无依赖的简单逻辑(打日志、CORS) |
| 类式 | 需要注入 Service 的中间件(认证、限流) |
| 挂载 | MiddlewareConsumer.apply(...).forRoutes(...) |
| 顺序 | apply() 顺序即执行顺序 |
| 终止 | res.send 后别忘 return |
动手练习
- 打日志:写一个 logger 中间件,记录 method/url/status/duration
- 鉴权:写一个
AuthMiddleware,校验 Authorization header - 挂载:让 AuthMiddleware 只对
/admin生效,日志对全部生效
下一章:第 7 章:异常过滤器 →