Skip to content
第 6 章 后端 ⏱ 12 分钟阅读

第 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

动手练习 ​

  1. 打日志:写一个 logger 中间件,记录 method/url/status/duration
  2. 鉴权:写一个 AuthMiddleware,校验 Authorization header
  3. 挂载:让 AuthMiddleware 只对 /admin 生效,日志对全部生效

下一章:第 7 章:异常过滤器 →

本站基于 VitePress 构建 · 由 StackHub 团队维护