你在 NestJS 里看到的 @UseGuards()、@UsePipes()、app.useGlobalInterceptors() 这些,本质上都在做一件事:
- 把一段"横切逻辑"挂到请求处理链上
比如:鉴权、参数校验、日志、统一返回体、统一异常格式......
这篇就用"人话"把三个问题讲清楚:
- NestJS 里可绑定的【元素】有哪些
- 全局绑定 vs 局部绑定:作用与区别
- 全局绑定的多种形式:各自原理/传参/差异/注意点,以及怎么选
本文所有结论都以 NestJS 官方文档为准(会在对应小节标注链接)。
目录
- NestJS 里能"绑定"的【元素】有哪些?
- 全局绑定 vs 局部绑定:作用与区别
- 全局绑定的多种形式:到底差在哪?
- 五类元素分别怎么绑、怎么传参、有哪些坑?
- 选型:什么时候用哪种绑定方式?
- 总结
1. NestJS 里能"绑定"的【元素】有哪些?
日常开发里,最常说的"绑定",基本就这五类(也是官方文档重点讲的五条链路):
- Middleware(中间件) :在路由处理前跑的一段函数/类,能拿到
req/res/next。
参考:Middleware - Guard(守卫) :决定"这次请求到底能不能进到 handler"。典型用来做鉴权/权限。
参考:Guards - Pipe(管道) :对入参做校验 或转换 (字符串转数字、DTO 校验等),发生在方法调用前。
参考:Pipes - Interceptor(拦截器) :更像 AOP,能在 handler 前后插逻辑、改返回值、做缓存、把异常映射成别的异常等。
参考:Interceptors - Exception Filter(异常过滤器) :专门兜异常,统一格式、打日志、屏蔽敏感信息等。
参考:Exception filters
如果你要一个"背诵版"的链路顺序,官方明确写过的一句是:
- Guard 在所有 Middleware 之后执行,并且在任何 Interceptor 或 Pipe 之前执行
参考:Guards - Hint
2. 全局绑定 vs 局部绑定:作用与区别(别背概念,直接按场景理解)
先把"范围"说清楚,后面选型才不容易绕晕。
- 局部绑定(Local / Scoped) :只影响"某个控制器 / 某个路由方法 / 某个参数"。
典型写法:@UseGuards()、@UsePipes()、@UseInterceptors()、@UseFilters(),以及 Pipe 还能绑到参数上。 - 全局绑定(Global) :影响"整个应用里所有 controller + 所有 route handler"。
典型写法:app.useGlobalXxx(...)、模块 providers 里用APP_XXX、Middleware 的app.use(...)/forRoutes('*')。
一句话区分:
- 局部绑定:像"给某个接口/模块单独加一条规则"
- 全局绑定:像"把规则写进公司制度,所有人默认都得遵守"
3. 全局绑定不止一种写法:到底差在哪?
这个是很多人纠结的核心:为什么全局还能写出两三种形式?我该用哪个?
3.1 main.ts 的 app.useGlobalXxx(new ...):直给、简单,但 DI 有坑
以 Pipe 为例,官方给过最直观的全局写法:
ts
// main.ts
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
参考:Pipes - Global scoped pipes
这种写法的本质是:你自己把实例 new 出来,挂到应用上。
- 优点:直观、配置集中、适合"纯配置型"的管道/拦截器(不需要注入任何 service)
- 最大的缺点 :官方明确说了------在模块外注册的全局 Pipe/Guard/Interceptor/Filter 无法注入依赖
参考:
还有个容易被忽略的"覆盖范围"问题:在混合应用(HTTP + WS/微服务)里,useGlobalPipes() / useGlobalGuards() 默认不一定 覆盖网关/微服务。官方在 pipes/guards 里都有提醒。
参考:
3.2 模块里用 APP_PIPE / APP_GUARD / APP_INTERCEPTOR / APP_FILTER:更"框架化",DI 友好
官方给的"解决 DI 问题"的标准姿势,就是把它注册成 provider:
ts
// app.module.ts(示例:全局 Pipe)
import { APP_PIPE } from '@nestjs/core';
@Module({
providers: [
{ provide: APP_PIPE, useClass: ValidationPipe },
],
})
export class AppModule {}
参考:Pipes - Global scoped pipes(APP_PIPE)
Guard / Interceptor / Filter 的写法完全一样,只是 token 变了:
- Guard:
APP_GUARD参考:Guards - Binding guards - Interceptor:
APP_INTERCEPTOR参考:Interceptors - Binding interceptors - Filter:
APP_FILTER参考:Exception filters - Binding filters
这种写法的本质是:交给 Nest DI 容器来创建实例。
- 优点:能注入依赖;更容易做可测试的设计;在复杂业务里更推荐
- 注意 :官方也强调------不管你在哪个 module 里写,它都是"真的全局",建议放在"该类定义所在的 module"
参考同上各章的 Hint(都提到了"choose the module where X is defined")
3.3 装饰器里传"类" vs 传"new 出来的实例":你其实是在选"谁来创建对象"
官方在多个章节都写过:装饰器里你可以传类 ,也可以传实例。
以 Guard 为例:
ts
@UseGuards(RolesGuard) // 传类:Nest 来实例化,可 DI
@UseGuards(new RolesGuard()) // 传实例:你来实例化,一般就别指望 DI 了
Pipe/Interceptor/Filter 也是同理(官方都写了"pass class enables dependency injection / pass in-place instance for customization"那套逻辑)。
简单粗暴的结论:
- 想要 DI :尽量传类(或用
APP_XXX) - 想要按接口定制参数 (比如某个
ParseIntPipe想改errorHttpStatusCode):就传new Xxx(options)
3.4 Middleware 的全局绑定更"特别":app.use() 很香,但它根本进不了 DI
官方对 middleware 的说明更直白:
app.use(logger)能一次绑到所有路由,但无法访问 DI 容器
参考:Middleware - Global middleware- 如果你需要 DI,就别用
app.use();改用 class middleware +.forRoutes('*')(它运行在 module 里,能注入)
参考同上(官方也给了替代方案)
4. 逐个元素讲清楚:怎么绑、怎么传参、有哪些坑
下面每个元素我都给你:能绑在哪些层级 + 全局的几种写法 + 需要注意的点 + 伪代码
4.1 Middleware(中间件)
能绑在哪些层级
- 模块/路由级 :
consumer.apply(...).forRoutes(...)(最常用) - 全局 :
app.use(...)(但不走 DI)
绑定伪代码
ts
// 1) 模块内绑定(推荐:可 DI)
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('cats'); // 只绑 /cats
consumer
.apply(LoggerMiddleware)
.forRoutes({ path: 'cats', method: RequestMethod.GET }); // 只绑 GET /cats
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'cats', method: RequestMethod.GET },
'cats/{*splat}',
)
.forRoutes(CatsController); // 除了排除的,其他都绑
}
}
// 2) 全局绑定(简单,但 DI 不可用)
const app = await NestFactory.create(AppModule);
app.use(logger); // logger 是 functional middleware
参考:Middleware - Applying middleware / Excluding routes / Global middleware
特别注意
- 不调用
next()请求会挂住 (官方原话就是"request will be left hanging")
参考:Middleware app.use()的全局 middleware 拿不到 DI (要 DI 就用.forRoutes('*')那套)
参考:Middleware - Global middleware- Express 与 Fastify 的 middleware 签名不一样(官方有 warning)
参考同上:Middleware - Warning
4.2 Guard(守卫)
能绑在哪些层级
- Controller 级 :
@UseGuards()写在类上 - Method 级 :
@UseGuards()写在方法上 - 全局 :
app.useGlobalGuards(...)或APP_GUARD
绑定伪代码
ts
// 局部:controller 级
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}
// 局部:method 级
@Post()
@UseGuards(RolesGuard)
create() {}
// 全局:main.ts(不走 DI)
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());
// 全局:APP_GUARD(走 DI,推荐)
@Module({
providers: [{ provide: APP_GUARD, useClass: RolesGuard }],
})
export class AppModule {}
特别注意
- 执行顺序 :Guard 在 middleware 之后,在 interceptor/pipe 之前
参考:Guards - Hint - 混合应用覆盖范围 :
useGlobalGuards()在 hybrid app 默认不覆盖网关/微服务(官方 Notice)
参考:Guards - Binding guards - 全局 + DI :要 DI 就别在
main.ts里new,用APP_GUARD
参考同上(官方写得很明确)
4.3 Pipe(管道)
Pipe 这块"绑的层级"最多,也是最容易写出花的。
能绑在哪些层级
- 参数级 :
@Param('id', ParseIntPipe)/@Body(new ValidationPipe()) - 方法级 :
@UsePipes(...) - Controller 级 :
@UsePipes(...)写在类上 - 全局 :
useGlobalPipes()或APP_PIPE
绑定伪代码
ts
// 参数级:把 id 转成 number,不行就直接 400
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {}
// 参数级:定制 options,就 new 一个实例
@Get(':id')
findOne(
@Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }))
id: number,
) {}
// 方法级:按接口传 schema(典型"每个接口一套校验规则")
@Post()
@UsePipes(new ZodValidationPipe(createCatSchema))
create(@Body() dto: CreateCatDto) {}
// 全局:main.ts(不走 DI)
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
// 全局:APP_PIPE(走 DI,推荐)
@Module({
providers: [{ provide: APP_PIPE, useClass: ValidationPipe }],
})
export class AppModule {}
参考:Pipes - Binding pipes / Global scoped pipes
特别注意
- Pipe 抛异常会进入异常层处理 (官方叫 exceptions zone),抛了异常 handler 就不会执行
参考:Pipes - Hint - 混合应用覆盖范围 :
useGlobalPipes()在 hybrid app 下默认不覆盖网关/微服务(官方 Notice)
参考:Pipes - Global scoped pipes - 全局 + DI :同样要用
APP_PIPE(官方直接点名)
参考同上
4.4 Interceptor(拦截器)
能绑在哪些层级
- Controller / Method 级 :
@UseInterceptors(...) - 全局 :
useGlobalInterceptors()或APP_INTERCEPTOR
绑定伪代码
ts
// 局部:controller 级
@UseInterceptors(LoggingInterceptor)
export class CatsController {}
// 全局:main.ts(不走 DI)
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());
// 全局:APP_INTERCEPTOR(走 DI,推荐)
@Module({
providers: [{ provide: APP_INTERCEPTOR, useClass: LoggingInterceptor }],
})
export class AppModule {}
参考:Interceptors - Binding interceptors
特别注意
- 拦截器里如果你不调用
next.handle(),handler 根本不会执行 (官方写得很直)
参考:Interceptors - Call handler - 响应映射有个坑 :官方警告过,直接用
@Res()这种"库特定响应策略"会让 response mapping 不工作
参考:Interceptors - Response mapping(Warning) - 全局 + DI :还是那句,用
APP_INTERCEPTOR
参考:Interceptors - Binding interceptors
4.5 Exception Filter(异常过滤器)
能绑在哪些层级
- Method / Controller 级 :
@UseFilters(...) - 全局 :
useGlobalFilters()或APP_FILTER
绑定伪代码
ts
// 局部:method 级
@Post()
@UseFilters(HttpExceptionFilter) // 推荐传类,让 Nest 复用实例
create() {}
// 全局:main.ts(不走 DI)
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
// 全局:APP_FILTER(走 DI,推荐)
@Module({
providers: [{ provide: APP_FILTER, useClass: HttpExceptionFilter }],
})
export class AppModule {}
参考:Exception filters - Binding filters
特别注意
- 官方建议"尽量传类而不是传实例" ,原因是可复用、内存更友好
参考:Exception filters - Hint(Prefer applying filters by using classes) useGlobalFilters()不会给 gateways / hybrid apps 设置过滤器 (官方 Warning)
参考同上:Exception filters - Warning- 如果你写了一个
@Catch()什么都不填的"兜底过滤器" ,并且还写了某些"只抓特定异常"的过滤器,官方提醒:兜底的要先声明
参考:Exception filters - Catch everything(Warning)
5. 到底怎么选?给你一套"能直接落地"的决策规则
你可以按这几个问题来选,基本不踩坑。
5.1 这段逻辑是不是"所有接口都必须有"?
- 是(比如统一校验/统一返回体/统一异常格式/全局鉴权):倾向全局
- 不是(只对某几个接口/某个模块生效):局部绑定,别污染全局
5.2 这段逻辑要不要注入 Service / Config / DB / Cache?
- 要 DI :
- Guard/Pipe/Interceptor/Filter:优先用
APP_GUARD / APP_PIPE / APP_INTERCEPTOR / APP_FILTER - Middleware:优先用 class middleware +
consumer.apply(...).forRoutes('*')
- Guard/Pipe/Interceptor/Filter:优先用
- 不要 DI :
- 你就可以用
main.ts的useGlobalXxx(new ...)或app.use(...),写起来最快
- 你就可以用
(这些 DI 限制和替代方案,官方都写在对应章节里了:
Pipes、Guards、Interceptors、Exception filters、Middleware)
5.3 你需要"每个接口参数不一样"吗?
- 需要 (比如某个 Pipe 要带不同 options、或者每个接口校验 schema 不一样):局部
new Xxx(options)更合适 - 不需要(全站统一同一套配置):全局注册一次,别每个方法都写一遍
5.4 你项目是不是 hybrid(HTTP + WS/微服务)?
如果是,别默认以为 useGlobalXxx() 就全覆盖。官方在 pipes/guards/filters 里都写了"hybrid app"的注意点,建议你在项目里明确验证一下覆盖范围:
6. 总结
- 能绑定的核心元素就 5 个:Middleware、Guard、Pipe、Interceptor、Exception Filter。
- 局部绑定解决"精准控制":只对某个 controller / method / param 生效,最不容易"误伤"别的模块。
- 全局绑定解决"统一规则":所有接口默认生效,但你要对"DI 能不能用、hybrid 覆盖范围"保持敏感。
- 全局绑定最重要的分水岭是 DI :
main.ts的useGlobalXxx(new ...):快,但基本不走 DI- module 里的
APP_XXX:更工程化,DI 友好,复杂项目更推荐 - middleware 的
app.use():最简单,但拿不到 DI;要 DI 就.forRoutes('*')
- 装饰器传"类"还是"实例":你其实是在决定"让 Nest 创建对象(可 DI、可复用)"还是"自己 new(方便定制参数)"。