请求生命周期
了解中间件之前,需要先对整个 请求生命周期 有一定的认识:
请求进入 → 中间件 → 守卫 → 拦截器 → 管道 → 控制器 → 服务 → 拦截器 → 异常过滤器 → 服务器响应
-
请求进入
-
中间件
- 2.1. 全局绑定的中间件
- 2.2. 模块绑定的中间件
-
守卫
- 3.1 全局守卫
- 3.2 控制器守卫
- 3.3 路由守卫
-
拦截器(控制器前)
- 4.1 全局拦截器
- 4.2 控制器拦截器
- 4.3 路由拦截器
-
管道
- 5.1 全局管道
- 5.2 控制器管道
- 5.3 路由管道
- 5.4 路由参数管道
-
控制器(方法处理程序)
-
服务(如果存在)
-
拦截器(请求后)
- 8.1 路由拦截器
- 8.2 控制器拦截器
- 8.3 全局拦截器
-
异常过滤器
- 9.1 路由过滤器
- 9.2 控制器过滤器
- 9.3 全局过滤器
-
服务器响应
中间件
一、它是什么
官网描述:
Middleware是在路由处理程序之前 调用的函数。中间件函数可以访问请求和响应对象,以及应用程序的请求-响应周期中的next()中间件函数。通常,next 中间件函数由一个名为next的变量表示。

Nest中间件默认情况下与express中间件等效。以下来自官方express文档的描述介绍了中间件的功能:
中间件函数可以执行以下任务:
- 执行任何代码。
- 对请求和响应对象进行更改。
- 结束请求-响应周期。
- 调用堆栈中的下一个中间件函数。
- 如果当前中间件函数未结束请求-响应周期,则必须调用
next()将控制权传递给下一个中间件函数。否则,请求将被搁置。
白话文:用来在请求到达路由处理器之前或之后执行特定的逻辑。
二、 使用
参考官网:nestjs.inode.club/middleware
-
你的第一个中间件
logger.middleware.tstypescriptimport { 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('Request...'); next(); } } -
在
app.module.ts中使用typescriptimport { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { CatsModule } from './cats/cats.module'; import { CatsController } from './cats/cats.controller' @Module({ imports: [CatsModule], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('cats'); // .forRoutes({ path: 'cats', method: RequestMethod.GET }); // .forRoutes({ path: 'cats/*', method: RequestMethod.ALL }); // .forRoutes(CatsController); } }注意以上代码,forRoutes 有多中写法,还有 路由排除:exclude
-
支持 - 路由通配符写法:路由路径
'ab*cd'将匹配abcd、ab_cd、abecd等等。字符?、+、*和()可以在路由路径中使用,它们是正则表达式的子集。连字符(-)和点号(.)在基于字符串的路径中被解释为字面量。 -
exclude 路由排除:
phpconsumer .apply(LoggerMiddleware) .exclude( { path: 'cats', method: RequestMethod.GET }, { path: 'cats', method: RequestMethod.POST }, 'cats/(.*)', ) .forRoutes(CatsController);
-
-
函数式中间件
没有成员,没有额外的方法,也没有依赖关系 时,可以使用它;
vbscriptimport { Request, Response, NextFunction } from 'express'; export function logger(req: Request, res: Response, next: NextFunction) { console.log(`Request...`); next(); }; -
多个中间件
正如上面所述,为了绑定多个按顺序执行的中间件,只需在
apply()方法内提供一个逗号分隔的列表:scssconsumer.apply(cors(), helmet(), logger).forRoutes(CatsController); -
全局中间件
如果我们想要一次性将中间件绑定到每个已注册的路由上,我们可以使用
INestApplication实例提供的use()方法:ini// main.ts 中 const app = await NestFactory.create(AppModule); app.use(logger); await app.listen(3000);
三、使用场景
主要作用:
- 请求预处理:在请求到达控制器之前对请求进行检查、修改或增强。
- 响应后处理:在响应发送给客户端之前对响应进行修改或记录。
- 流程控制:决定是否继续处理请求或直接返回响应。
使用场景:
Express和fastify处理中间件的方式不同,并提供不同的方法签名,详情请阅读这里。
1. 日志记录
typescript
// logging.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class LoggingMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: Function) {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url} - ${duration}ms`);
});
next();
}
}
2. 身份验证和授权
typescript
// auth.middleware.ts
import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: Function) {
const token = req.headers['authorization'];
if (!token) {
throw new UnauthorizedException('Token is required');
}
// 验证 token 逻辑
const user = this.validateToken(token);
if (!user) {
throw new UnauthorizedException('Invalid token');
}
// 将用户信息附加到请求对象
(req as any).user = user;
next();
}
private validateToken(token: string) {
// JWT 验证逻辑
return { id: 1, username: 'john' };
}
}
3. 请求数据验证和转换
typescript
// validation.middleware.ts
import { Injectable, NestMiddleware, BadRequestException } from '@nestjs/common';
@Injectable()
export class ValidationMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: Function) {
// 验证 Content-Type
if (req.headers['content-type'] !== 'application/json') {
throw new BadRequestException('Only JSON content is allowed');
}
// 验证请求体大小
const contentLength = parseInt(req.headers['content-length']);
if (contentLength > 1024 * 1024) { // 1MB
throw new BadRequestException('Request body too large');
}
next();
}
}
4. 限流和频率控制
typescript
// rate-limiting.middleware.ts
import { Injectable, NestMiddleware, TooManyRequestsException } from '@nestjs/common';
@Injectable()
export class RateLimitingMiddleware implements NestMiddleware {
private requests = new Map<string, number[]>();
use(req: Request, res: Response, next: Function) {
const ip = req.ip;
const now = Date.now();
const windowMs = 15 * 60 * 1000; // 15分钟
const maxRequests = 100; // 最大100个请求
const userRequests = this.requests.get(ip) || [];
// 清理过期的请求记录
const recentRequests = userRequests.filter(time => now - time < windowMs);
if (recentRequests.length >= maxRequests) {
throw new TooManyRequestsException('Too many requests');
}
recentRequests.push(now);
this.requests.set(ip, recentRequests);
// 设置限流头信息
res.setHeader('X-RateLimit-Limit', maxRequests);
res.setHeader('X-RateLimit-Remaining', maxRequests - recentRequests.length);
next();
}
}
5. CORS 处理
typescript
// cors.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class CorsMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: Function) {
res.setHeader('Access-Control-Allow-Origin', 'https://example.com');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Credentials', 'true');
// 处理预检请求
if (req.method === 'OPTIONS') {
res.status(204).send();
return;
}
next();
}
}
6. 数据压缩和缓存
typescript
// compression.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import * as compression from 'compression';
@Injectable()
export class CompressionMiddleware implements NestMiddleware {
private compression = compression();
use(req: Request, res: Response, next: Function) {
// 对响应进行压缩
this.compression(req, res, next);
}
}
7. API 版本控制
typescript
// versioning.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class VersioningMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: Function) {
const version = req.headers['api-version'] || 'v1';
// 根据版本号设置不同的行为
(req as any).apiVersion = version;
// 可以在这里根据版本重写 URL 或设置不同的逻辑
if (version === 'v2') {
// v2 特定的预处理
}
next();
}
}
8. 多租户支持
typescript
// tenant.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class TenantMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: Function) {
const tenantId = req.headers['x-tenant-id'] || req.hostname;
if (!tenantId) {
throw new Error('Tenant identification required');
}
// 设置租户上下文
(req as any).tenant = {
id: tenantId,
// 可以根据租户ID加载特定配置
config: this.loadTenantConfig(tenantId)
};
next();
}
private loadTenantConfig(tenantId: string) {
// 加载租户特定配置
return { database: `tenant_${tenantId}`, theme: 'default' };
}
}
四、总结
中间件的优势
- 代码复用:相同的逻辑可以在多个路由间共享
- 关注点分离:将横切关注点与业务逻辑分离
- 可维护性:中间件逻辑集中管理,易于维护
- 灵活性:可以轻松添加、移除或修改中间件
- 性能优化:可以在早期拒绝无效请求,减少不必要的处理
使用建议
- 中间件应该保持轻量级,避免复杂的业务逻辑
- 对于复杂的业务验证,考虑使用守卫(Guards)或管道(Pipes)
- 使用异常过滤器来处理中间件中抛出的异常
- 考虑中间件的执行顺序对性能的影响