前言
在本章开始之前,我们需要先了解 NestJS 的 请求管线(Request Pipeline)机制。 其核心概念包括:
- Middleware 中间件
- Guard 守卫
- Pipe 管道
- Interceptor 拦截器
- Exception Filter 异常过滤
初看可能陌生,但掌握后能让代码更优雅、易维护。
痛点:如果没有这些机制,会怎样?
假设你只用最原始的 Controller 调用:
ts
@Controller('user')
export class UserController {
@Post('create')
create(@Body() dto: CreateUserDto) {
if (!dto.name) throw new Error('missing name');
if (!this.authService.verify(req.headers.token)) throw new Error('unauthorized');
const start = Date.now();
const result = this.userService.create(dto);
console.log('耗时', Date.now() - start);
return { data: result };
}
}
这只是一个普通的业务逻辑,但你会发现:
- 参数验证重复
- 权限控制混杂业务逻辑
- 异常处理零散
- 日志和监控难统一
- 小改动可能破坏流程
Nest 的 "分层执行管线"
Nest 把整个请求的处理过程,拆成 一条管线(Pipeline) ,每个环节处理一种类型的问题。
这就是 Middleware → Guard → Pipe → Interceptor → Controller → Exception Filter。
| 机制 | 出发点(要解决的问题) | 执行阶段 | 典型关键词 | 失败后走向 |
|---|---|---|---|---|
| Middleware | 路由匹配前的全局逻辑:日志、CORS、body解析、静态文件、token提取等。Express 层面的问题。 | Controller 之前(路由匹配前) | 框架层面、通用逻辑 | 中断请求 |
| Guard | "这个请求是否被允许进入?"------访问控制与授权判断。 | Controller 之前(匹配后执行) | 权限控制、角色、token验证 | Forbidden |
| Pipe | "请求参数是否有效?类型是否正确?"------校验与转换。 | Controller 参数解析阶段 | DTO校验、类型转换 | BadRequest |
| Interceptor | "执行逻辑前后做些横切操作"------拦截执行、记录耗时、包裹响应。 | Controller 调用前后 | AOP、日志、响应包装 | Exception Filter |
| Exception Filter | "错误发生了,如何优雅返回?"------统一异常格式。 | Controller 之后(异常时) | 错误捕获、返回 | 结束请求 |
归纳一下:
- Middleware:我在门外 ------ 拦截所有请求(Express层)
- Guard:我在门口 ------ 判断是否能进当前 Controller
- Pipe:我在门槛 ------ 校验和格式化参数
- Interceptor:我在屋内 ------ 监听整个执行过程(包裹Controller)
- Exception Filter:我在出口 ------ 接住一切错误
本章我们将从管线的第一环节 Middleware 开始,详细讲解它的使用与原理。
Middleware 深入讲解
在 NestJS 的请求管线中,Middleware 是第一道防线,在路由匹配之前执行,先于 Controller,可做日志记录、CORS 处理、请求体解析、Token 提取等
Middleware 类型与使用
NestJS 的 Middleware 主要有两种类型:
1. 函数式 Middleware
- 最简单的形式,直接是一个函数。
- 不支持依赖注入。
- 使用场景:快速拦截、日志打印、简单处理请求。
ts
import { Request, Response, NextFunction } from 'express';
function logger(req: Request, res: Response, next: NextFunction) {
console.log(`[${req.method}] ${req.url}`);
next(); // 调用 next() 将请求传给下一个 Middleware 或 Controller
}
// 全局注册
app.use(logger);
2. 类式 Middleware(推荐)
- 实现 NestMiddleware 接口。
- 支持依赖注入(可以通过 constructor 注入 Service)。
- 使用场景:业务相关的预处理、模块化逻辑。
ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { AuthService } from './auth.service';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
constructor(private authService: AuthService) {}
use(req: Request, res: Response, next: NextFunction) {
const token = req.headers['authorization'];
if (!this.authService.verify(token)) {
return res.status(401).json({ message: 'Unauthorized' });
}
next();
}
}
注册方式
1. 全局 Middleware
ts
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
function logger(req, res, next) {
console.log(`[${req.method}] ${req.url}`);
next();
}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 全局注册函数式 middleware
app.use(logger);
await app.listen(3000);
}
bootstrap();
2. 模块级 Middleware
ts
// user.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { UserController } from './user.controller';
import { AuthMiddleware } from './auth.middleware';
@Module({
controllers: [UserController],
})
export class UserModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthMiddleware) // 指定中间件
.forRoutes(UserController); // 模块级:拦截 UserController 内所有路由
}
}
3. 路由级 Middleware
ts
// user.module.ts
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { UserController } from './user.controller';
import { LoggerMiddleware } from './logger.middleware';
@Module({
controllers: [UserController],
})
export class UserModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes(
{ path: 'user/create', method: RequestMethod.POST }, // 仅拦截 POST /user/create
{ path: 'user/:id', method: RequestMethod.GET }, // 仅拦截 GET /user/:id
);
}
}
小结:
| 类型 | 注册方式 | 拦截范围 | 适用场景 |
|---|---|---|---|
| 全局 Middleware | app.use(fn) | 应用内所有路由 | 日志、CORS、全局鉴权 |
| 模块级 Middleware | consumer.apply(...).forRoutes(Controller) | 模块内所有路由 | 模块统一逻辑,支持 DI |
| 路由级 Middleware | consumer.apply(...).forRoutes({ path, method }) | 指定接口 | 精确拦截特定路由或方法 |
Express middleware
为什么要讲 Express middleware?
在讲解 NestJS 的中间件之前,我们必须先理解 Express 的中间件模型。
原因在于:
-
Nest 默认底层是基于 Express 框架构建 的(除非手动切换为 Fastify 适配器);
-
因此,Nest 的中间件机制本质上就是 对 Express middleware 的封装与模块化抽象;
-
理解 Express middleware 的执行时机、参数形式和调用链机制,有助于我们更清楚 Nest 的内部工作原理。
换句话说,Nest 的 Middleware 是"更结构化的 Express Middleware" 。
如果你知道 app.use() 是如何工作的,你就能立刻明白 @MiddlewareConsumer().apply() 背后的逻辑。
Express middleware 的使用
在 Express 中,中间件(Middleware)是一个函数,它能访问 请求对象 (req) 、响应对象 (res) 和 下一个中间件函数 (next) 。
定义中间件Logger 如下:
ts
function logger(req, res, next) {
console.log(`Request... ${req.method} ${req.url}`);
next(); // 调用下一个中间件,否则请求会被挂起
}
- 用法一:全局中间件
ts
const express = require('express');
const app = express();
app.use(logger); // 全局中间件
app.get('/users', (req, res) => res.send('User list'));
app.listen(3000);
- 用法二:匹配
/users*的请求
ts
const express = require('express');
const app = express();
app.use('/users', logger); // 只作用于具体某个路径
app.get('/users', (req, res) => res.send('User list'));
app.listen(3000);
- 用法三:匹配
router模块
ts
// admin.module.js
const express = require('express');
const adminRouter = express.Router();
const { adminAuthMiddleware } = require('./admin.middleware');
adminRouter.use(adminAuthMiddleware);
adminRouter.get('/dashboard', (req, res) => res.send('Admin Dashboard'));
adminRouter.get('/settings', (req, res) => res.send('Admin Settings'));
module.exports = { adminRouter }
// user.module.js
const userRouter = express.Router();
const { userLoggerMiddleware } = require('./user.middleware');
userRouter.use(userLoggerMiddleware);
userRouter.get('/profile', (req, res) => res.send('User Profile'));
userRouter.get('/settings', (req, res) => res.send('User Settings'));
module.exports = { userRouter }
// app.js
const express = require('express');
const app = express();
const { adminRouter } = require('./admin.module');
const { userRouter } = require('./user.module');
app.use('/admin', adminRouter); // admin 模块带 auth 中间件,且admin模块的路由前缀为/admin
app.use('/user', userRouter); // user 模块带 logger 中间件,且user模块的路由前缀为/user
Express 中间件是整个框架的灵魂,它形成了一个"洋葱模型(Onion Model) "的请求管线,所有逻辑都在这个链上依次流转。
Nestjs middleware实现原理
其实 Nest 没有重新发明中间件,而是在 Express 机制上叠加了模块系统和依赖注入能力。
这一章不再重复依赖注入的实现逻辑了,从下面可以看到源码中最终将中间件挂载在底层httpServer(express)上是通过 app.use(path, middlewareFunction) 来实现的。
ts
private async registerHandler(
applicationRef: HttpServer,
routeInfo: RouteInfo,
proxy: <TRequest, TResponse>(
req: TRequest,
res: TResponse,
next: () => void,
) => void,
) {
const { method } = routeInfo;
const paths = this.routeInfoPathExtractor.extractPathsFrom(routeInfo);
const isMethodAll = isRequestMethodAll(method);
const requestMethod = RequestMethod[method];
// 这里的 createMiddlewareFactory 返回的是 app.use, 如果是在解析router的过程中则返回的是 controller 里的method.
const router = await applicationRef.createMiddlewareFactory(method);
const middlewareFunction = isMethodAll
? proxy
: <TRequest, TResponse>(
req: TRequest,
res: TResponse,
next: () => void,
) => {
if (applicationRef.getRequestMethod?.(req) === requestMethod) {
return proxy(req, res, next);
}
return next();
};
const pathsToApplyMiddleware = [] as string[];
paths.some(path => path.match(/^\/?$/))
? pathsToApplyMiddleware.push('/')
: pathsToApplyMiddleware.push(...paths);
pathsToApplyMiddleware.forEach(path => router(path, middlewareFunction));
}
使用 Nest Middleware 时有哪些注意点:
-
异步中间件支持度:
Express 原生并不直接支持异步中间件,常用做法如下:
tsconst asyncHandler = fn => (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; app.use(asyncHandler(async (req, res, next) => { await someAsyncTask(); next(); }));而 Nest 允许 middleware 写成 async use(req, res, next)。
-
中间件执行顺序:
Express中,中间件的执行顺序就是注册顺序; 但在Nest中,它不直接暴露 app.use() 顺序,而是遵循以下三层规则👇:
-
规则 1:在同一个模块中,中间件的顺序 = consumer.apply() 调用顺序
tsexport class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) // ✅ 第 1 个执行 .forRoutes('*'); consumer .apply(AuthMiddleware) // ✅ 第 2 个执行 .forRoutes('admin'); } } -
规则 2:跨模块时,顺序由模块的加载顺序决定
假设你的模块结构如下,每个模块都实现了自己的中间件:
ts@Module({ imports: [AuthModule, LoggerModule], }) export class AppModule {}那么请求时执行顺序是: AuthMiddleware → LoggerMiddleware → Controller
-
规则 3:全局中间件最先执行
如果你通过 app.use() 注册中间件(即在 main.ts 中写):
tsconst app = await NestFactory.create(AppModule); app.use(cors()); app.use(helmet());那么这些会在所有模块中间件之前执行。
-
-
错误捕获:
- Middleware 内抛出的异常如果不捕获,会交给 Nest 的 Exception Filter
总结:
NestJS 的 Middleware 是请求管线的第一道环节,用于框架层面的请求前处理(如日志、CORS、body解析、Token 提取等)。它基于底层 HTTP 框架(Express / Fastify),并对中间件进行了模块化封装,支持依赖注入和路由级控制,从而在大型项目中更灵活和可维护。
Middleware 的源码主要在 这里,之所以没有花很多篇幅去讲这个文件的实现,是觉得源码涉及细节较多,但对理解核心原理不是必须;如果后面有需要,会再细化这方面的知识点。