Skip to content
第 181 / 250 章Node⏱ 12 分钟阅读

第 181 章:中间件(Middleware)

学习目标

  • 理解 Middleware 的作用
  • 掌握函数式与类式中间件
  • 学会常用场景(请求日志、CORS、压缩)
  • 理解中间件执行顺序

一、什么是 Middleware

Middleware 是请求进入路由处理器之前执行的函数,可以访问 request 和 response。

二、函数式中间件

2.1 定义

typescript
// 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.2 使用

typescript
// app.module.ts
@Module({
  imports: [],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(logger).forRoutes('*');   // 所有路由
  }
}

三、类式中间件

3.1 定义

typescript
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();
  }
}

3.2 注册

typescript
@Module({})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes('*');
  }
}

提示

类式中间件可以注入依赖(@Injectable()),适合复杂场景。

四、路由匹配

4.1 路径匹配

typescript
consumer.apply(LoggerMiddleware).forRoutes('users');           // /users
consumer.apply(LoggerMiddleware).forRoutes('users/*');         // /users/xxx
consumer.apply(LoggerMiddleware).forRoutes('*');               // 所有

4.2 控制器匹配

typescript
consumer.apply(AuthMiddleware).forRoutes(UserController);
consumer.apply(LoggerMiddleware).forRoutes(UserController, OrderController);

4.3 指定方法

typescript
import { RequestMethod } from '@nestjs/common';

consumer
  .apply(AuthMiddleware)
  .forRoutes({ path: 'users', method: RequestMethod.GET });

4.4 排除路由

typescript
consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: 'health', method: RequestMethod.GET },
    'auth/login',
  )
  .forRoutes('*');

五、多个中间件

5.1 顺序执行

typescript
consumer.apply(Middleware1, Middleware2, Middleware3).forRoutes('*');
// 执行顺序:Middleware1 → Middleware2 → Middleware3 → Controller

5.2 不同路径不同中间件

typescript
configure(consumer: MiddlewareConsumer) {
  consumer
    .apply(LoggerMiddleware)
    .forRoutes('*')
    .apply(AuthMiddleware)
    .forRoutes('users/*')
    .apply(AdminMiddleware)
    .forRoutes('admin/*');
}

六、常用中间件实战

6.1 请求 ID 中间件

typescript
import { v4 as uuidv4 } from 'uuid';

@Injectable()
export class RequestIdMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const id = req.headers['x-request-id'] || uuidv4();
    req['requestId'] = id;
    res.setHeader('X-Request-Id', id);
    next();
  }
}

// 在 Controller 中使用
@Get()
findAll(@Req() req: Request) {
  console.log(req['requestId']);
}

6.2 请求体大小限制

typescript
@Injectable()
export class BodySizeMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const contentLength = parseInt(req.headers['content-length'] || '0', 10);
    if (contentLength > 1024 * 1024) {   // 1MB
      return res.status(413).json({ message: '请求体过大' });
    }
    next();
  }
}

6.3 简易 API Key 校验

typescript
@Injectable()
export class ApiKeyMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const apiKey = req.headers['x-api-key'];
    if (apiKey !== process.env.API_KEY) {
      return res.status(401).json({ message: 'Invalid API Key' });
    }
    next();
  }
}

6.4 简易访问日志

typescript
@Injectable()
export class AccessLogMiddleware implements NestMiddleware {
  private logger = new Logger('HTTP');

  use(req: Request, res: Response, next: NextFunction) {
    const start = Date.now();

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

    next();
  }
}

6.5 简易 Rate Limit

typescript
@Injectable()
export class RateLimitMiddleware implements NestMiddleware {
  private visits = new Map<string, number[]>();

  use(req: Request, res: Response, next: NextFunction) {
    const ip = req.ip;
    const now = Date.now();
    const window = 60_000;   // 1 分钟
    const limit = 100;

    const times = (this.visits.get(ip) || []).filter(t => now - t < window);

    if (times.length >= limit) {
      return res.status(429).json({ message: 'Too many requests' });
    }

    times.push(now);
    this.visits.set(ip, times);

    res.setHeader('X-RateLimit-Limit', String(limit));
    res.setHeader('X-RateLimit-Remaining', String(limit - times.length));

    next();
  }
}

七、全局中间件 vs 路由中间件

方式用法
路由级consumer.apply(...).forRoutes(...)
全局(注册)app.use(LoggerMiddleware)
全局(类)APP_MIDDLEWARE Provider

7.1 全局函数中间件

typescript
// main.ts
const app = await NestFactory.create(AppModule);
app.use(cors());             // cors 库
app.use(helmet());           // helmet 库
app.use(compression());      // compression 库
app.use(express.json({ limit: '1mb' }));

7.2 全局类中间件

typescript
@Module({
  providers: [
    {
      provide: APP_MIDDLEWARE,
      useClass: LoggerMiddleware,
    },
  ],
})
export class AppModule {}

八、第三方中间件

8.1 CORS

bash
pnpm add cors
typescript
// main.ts
import cors from 'cors';
app.use(cors({
  origin: ['http://localhost:5173'],
  credentials: true,
}));

8.2 Helmet(安全头)

bash
pnpm add helmet
typescript
// main.ts
import helmet from 'helmet';
app.use(helmet());

8.3 Compression(压缩)

bash
pnpm add compression
typescript
// main.ts
import compression from 'compression';
app.use(compression());
bash
pnpm add cookie-parser
typescript
// main.ts
import cookieParser from 'cookie-parser';
app.use(cookieParser('secret'));

九、中间件 vs Guard vs Interceptor

类型时机可访问
Middleware路由前req, res, next
Guard路由前ExecutionContext
Interceptor路由前后ExecutionContext, RxJS
功能推荐
请求日志Middleware
鉴权Guard
响应转换Interceptor
请求体解析Middleware

十、本章小结

用法关键
函数中间件app.use(fn)
类中间件consumer.apply(Middleware).forRoutes()
路径匹配'users/*' / '*'
方法匹配RequestMethod.GET
全局注册APP_MIDDLEWARE
常用三方cors / helmet / compression

动手练习

  1. 实现一个请求日志中间件
  2. 写一个 RateLimit 中间件,限制 60s 内最多 100 次
  3. 用 helmet + cors + compression 配置 main.ts
  4. 实现一个请求 ID 中间件

推荐阅读


下一章:第 182 章:异常过滤器(Exception Filter)

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