第一次接触 NestJS,很容易有一个感觉:
概念很多。
Module、Controller、Provider、Guard、Pipe、Interceptor、Exception Filter......
再加上一堆装饰器:
ts
@Module()
@Controller()
@Injectable()
@Get()
@Post()
@UseGuards()
@UseInterceptors()
如果只是跟着项目代码一个个记,很容易知道"怎么写",却说不清:
为什么 NestJS 要把一个请求拆成这么多层?
换个角度看,NestJS 最核心的内容其实可以分成两条线。
第一条是应用结构:
text
Application
↓
Module
↓
Controller
↓
Provider / Service
↓
Database / External API
它解决的是:
代码、业务能力和依赖怎么组织?
第二条是 Request Lifecycle(请求生命周期):
text
Request
↓
Middleware
↓
Guard
↓
Interceptor
↓
Pipe
↓
Controller
↓
Response
Controller 内部通常还会继续调用 Service、Repository 或其他 Provider。
如果执行过程中出现没有被处理的异常,则会进入 Exception Filter。
这篇文章就沿着这两条线,把 NestJS 中最常用、也是实际开发和面试中比较值得掌握的几个概念串起来。
1. NestJS 为什么会有这么多概念?
先不看 NestJS。
假设直接写一个创建订单接口:
ts
app.post('/orders', async (req, res) => {
// 检查登录状态
// 检查用户权限
// 校验订单参数
// 创建订单
// 写数据库
// 记录日志
// 处理异常
// 格式化返回数据
})
项目很小的时候,这样写并没有太大问题。
但业务越来越复杂以后,一个 Route Handler(路由处理函数)很容易慢慢变成:
text
Route Handler
├── Authentication
├── Authorization
├── Validation
├── Business Logic
├── Database
├── Logging
├── Error Handling
└── Response Transformation
代码仍然可以运行,但不同职责已经混在了一起。
以后想统一权限、统一参数校验、统一响应格式,甚至只是单独测试一段业务逻辑,都会越来越麻烦。
NestJS 的做法,是把这些职责放到更明确的位置:
text
Module(模块)
→ 组织业务能力
Controller(控制器)
→ 接收请求
Provider(提供者) / Service(服务)
→ 承载业务能力
Guard(守卫)
→ 判断请求能不能继续
Pipe(管道)
→ 校验和转换参数
Interceptor(拦截器)
→ 包裹执行过程
Exception Filter(异常过滤器)
→ 处理未捕获异常
所以学习 NestJS 时,比记住某个装饰器怎么写更重要的是:
一段逻辑为什么应该放在这一层,而不是另一层?
2. 一个 NestJS 应用是怎么启动的?
一个基础的 NestJS 项目通常从 main.ts 开始:
ts
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
await app.listen(3000)
}
bootstrap()
其中最关键的是:
ts
NestFactory.create(AppModule)
启动过程可以先简化成:
text
main.ts
↓
NestFactory.create(AppModule)
↓
加载 AppModule
↓
加载其他业务 Module
↓
注册 Controller / Provider
↓
建立依赖关系
↓
启动应用
AppModule 是整个模块体系的入口。
后面的 Controller、Service、Provider,也都需要通过 Module 进入 NestJS 的管理范围。
所以理解 NestJS 的应用结构,可以先从 Module 开始。
3. Module:NestJS 怎么组织业务能力?
一个稍微完整一点的项目,通常不会把所有功能都放进 AppModule。
例如:
text
AppModule
├── UserModule
├── AuthModule
├── OrderModule
└── PaymentModule
再看其中的 OrderModule:
text
OrderModule
├── OrderController
├── OrderService
├── OrderRepository
└── 其他 Order Provider
Module 可以看成一个业务能力边界。
例如:
ts
@Module({
imports: [],
controllers: [OrderController],
providers: [OrderService],
exports: [OrderService],
})
export class OrderModule {}
这里最需要理解的是四个字段。
controllers
ts
controllers: [OrderController]
声明当前 Module 中有哪些 Controller。
例如:
text
POST /orders
↓
OrderController
providers
ts
providers: [OrderService]
声明当前 Module 管理哪些 Provider。
OrderService 注册后,NestJS 才能把它纳入当前模块的依赖关系中。
imports
如果订单模块需要使用用户模块提供的能力:
ts
@Module({
imports: [UserModule],
})
export class OrderModule {}
关系就是:
text
OrderModule
│
│ imports
↓
UserModule
exports
如果 UserModule 希望把 UserService 提供给其他模块:
ts
@Module({
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
那么其他引入 UserModule 的模块,就可以使用它暴露出来的 UserService:
text
UserModule
│
│ exports UserService
↓
OrderModule
│
└── 使用 UserService
这几个字段可以这样记:
text
providers
→ 我这里有哪些能力
exports
→ 哪些能力允许其他 Module 使用
imports
→ 我要使用哪个 Module 暴露出来的能力
controllers
→ 当前 Module 有哪些请求入口
所以 Module 不只是给文件分目录。
它还决定:
一个能力属于哪里,以及哪些模块可以使用它。
4. Controller:HTTP 请求从哪里进入?
Module 负责组织能力,真正接住 HTTP 请求的通常是 Controller。
例如:
ts
@Controller('orders')
export class OrderController {
constructor(
private readonly orderService: OrderService,
) {}
@Post()
create(@Body() dto: CreateOrderDto) {
return this.orderService.create(dto)
}
}
这里:
ts
@Controller('orders')
声明 Controller 的路由前缀。
而:
ts
@Post()
表示当前方法处理 POST 请求。
于是:
text
POST /orders
↓
OrderController.create()
常见的 Controller 装饰器还有:
ts
@Get()
@Post()
@Put()
@Delete()
@Param()
@Query()
@Body()
这些 API 本身并不难。
更值得关注的是 Controller 的职责边界。
通常推荐:
text
Controller
↓
接收 HTTP 参数
↓
调用 Service
↓
返回结果
而不是:
text
Controller
├── 查询数据库
├── 写大量业务逻辑
├── 调多个第三方接口
├── 自己处理权限
└── 自己兜底所有异常
例如:
ts
@Post()
create(@Body() dto: CreateOrderDto) {
return this.orderService.create(dto)
}
Controller 负责 HTTP 层,真正的订单业务交给 OrderService。
5. Provider 与 DI:Service 为什么可以直接注入?
前面的 Controller 中有这样一段代码:
ts
constructor(
private readonly orderService: OrderService,
) {}
第一次看到时可能会有点奇怪:
OrderService明明没有手动new,它是从哪里来的?
这里就涉及 NestJS 很重要的一套机制:
Dependency Injection(依赖注入,简称 DI)。
5.1 先弄清楚什么叫"依赖"
假设 OrderController 创建订单时必须调用 OrderService:
text
OrderController
↓
需要
↓
OrderService
那么就可以说:
OrderController依赖OrderService。
如果不用框架,最直接的写法就是自己创建:
ts
export class OrderController {
private readonly orderService = new OrderService()
}
这时候 Controller 同时负责了两件事:
text
我要使用 OrderService
+
我自己负责创建 OrderService
代码当然可以运行。
但随着项目变复杂,OrderService 自己可能还依赖:
text
OrderRepository
CacheService
PaymentClient
如果全部自己创建,代码可能慢慢变成:
ts
const repository = new OrderRepository()
const cache = new CacheService()
const payment = new PaymentClient()
const orderService = new OrderService(
repository,
cache,
payment,
)
const controller = new OrderController(
orderService,
)
依赖越多,创建和维护这些对象就越麻烦。
5.2 DI 做了什么?
依赖注入换了一种思路:
类只声明"我需要什么",至于这个对象怎么创建,交给外部处理。
所以 Controller 只需要写:
ts
constructor(
private readonly orderService: OrderService,
) {}
相当于告诉 NestJS:
text
OrderController
│
│ 我需要 OrderService
↓
NestJS
NestJS 的 DI Container(依赖注入容器) 会根据已经注册的 Provider 找到 OrderService:
text
OrderController
│
│ 需要 OrderService
↓
Nest DI Container
│
├── 找到 OrderService Provider
│
├── 创建 / 获取实例
│
↓
注入 OrderController
所以"依赖注入"这个词虽然听起来比较抽象,实际做的事情可以概括成:
一个类需要另一个对象时,不自己创建,而是由外部把需要的对象提供进来。
在 NestJS 里,这个"外部"主要就是 DI Container。
5.3 Provider 又是什么?
最常见的 Provider 就是 Service:
ts
@Injectable()
export class OrderService {
create(dto: CreateOrderDto) {
// 创建订单
}
}
@Injectable() 让这个类可以参与 NestJS 的依赖注入体系。
但 Provider 并不只等于 Service。
下面这些能力都可能注册成 Provider:
text
Service
Repository
Factory
Adapter
External API Client
Cache Client
所以可以把 Provider 理解成:
交给 NestJS 管理,并且可以被其他对象注入使用的能力。
5.4 为什么要这样做?
最直观的好处是:
- 类不再负责创建自己的依赖
- 单元测试时更容易替换 Mock
- 更容易替换具体实现
- Provider 的实例和生命周期可以统一管理
例如测试时,可以把:
text
OrderService
换成:
text
MockOrderService
而 Controller 本身不需要跟着修改。
5.5 再往下一层:Custom Provider
Provider 也不一定只能这样注册:
ts
providers: [OrderService]
还可以:
ts
{
provide: 'PAYMENT_SERVICE',
useClass: StripePaymentService,
}
这里建立的是一层映射:
text
Injection Token(注入令牌)
↓
具体 Provider
NestJS 还支持:
text
useClass
useValue
useFactory
useExisting
这部分属于 DI 更深入的内容,这篇先不展开。
到这里,可以把 Module 和 DI 的职责分开:
text
Module
→ 决定 Provider 在哪里注册、能被谁使用
DI Container
→ 负责找到、创建并注入 Provider

6. 一次 HTTP 请求到底怎么经过 NestJS?
前面解决的是:
NestJS 怎么组织代码,以及对象之间怎么建立依赖。
接下来换到第二条线:
一个请求真正进入应用以后,会经过哪些处理?
仍然使用同一个接口:
http
POST /orders
先看整体链路:
text
HTTP Request
↓
Middleware
↓
Guard
↓
Interceptor Before
↓
Pipe
↓
Controller
↓
Interceptor After
↓
HTTP Response
Controller 内部通常还会调用 Service、Repository 等 Provider,这属于应用自己的业务调用过程。
如果请求执行过程中出现未处理异常:
text
Exception
↓
Exception Filter
↓
Error Response
接下来的几个组件,就是在这条链路的不同位置解决不同问题。

7. Middleware:请求刚进来先做什么?
Middleware(中间件)位于请求链比较靠前的位置。
假设希望每个请求先生成一个 Request ID,或者记录最基础的访问日志,就很适合放在这里。
例如:
ts
@Injectable()
export class LoggerMiddleware
implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(req.method, req.url)
next()
}
}
常见用途包括:
text
Request ID
基础访问日志
Header 处理
请求上下文初始化
例如:
text
POST /orders
↓
RequestIdMiddleware
↓
生成 requestId
↓
继续执行
Middleware 更接近底层 HTTP 请求。
但如果接下来需要判断:
当前用户到底有没有权限访问这个具体接口?
这件事交给 Guard 会更合适。
8. Guard:这个请求能不能继续?
Guard(守卫)的职责比较明确:
判断当前请求是否允许继续进入后续 Handler(处理函数)。
假设 POST /orders 只有登录用户才能访问:
ts
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext) {
const request = context
.switchToHttp()
.getRequest()
return Boolean(request.user)
}
}
可以简化成:
text
POST /orders
↓
AuthGuard
│
├── true → 继续
│
└── false → 拒绝
Guard 常用于:
text
Authentication(认证)
Authorization(授权)
JWT
Role
Permission
API Key
例如:
text
POST /orders
↓
AuthGuard
↓
检查登录状态
↓
RoleGuard
↓
检查 create:order 权限
↓
继续执行
Guard 还可以作用在不同范围:
text
Global
↓
Controller
↓
Route
例如整个 Controller:
ts
@UseGuards(AuthGuard)
@Controller('orders')
export class OrderController {}
也可以只限制某一个接口。
如果一段逻辑主要回答:
"这个请求有没有资格继续?"
Guard 通常就是合适的位置。
9. Pipe:通过权限以后,参数能不能用?
权限检查通过后,下一步就是客户端带进来的参数。
Pipe(管道)主要负责两件事情:
text
Validation(校验)
+
Transformation(转换)
参数转换
例如:
ts
@Get(':id')
findOne(
@Param('id', ParseIntPipe) id: number,
) {}
请求:
text
GET /orders/123
URL 里的参数原本是字符串:
ts
'123'
经过 ParseIntPipe 后,可以得到:
ts
123
DTO + ValidationPipe
实际项目里更常见的是 DTO(Data Transfer Object,数据传输对象)校验。
例如:
ts
export class CreateOrderDto {
@IsString()
productId: string
@IsInt()
@Min(1)
quantity: number
}
然后配置:
ts
app.useGlobalPipes(
new ValidationPipe({
transform: true,
whitelist: true,
}),
)
这样请求进入 Controller 之前,就可以先完成:
text
字段校验
类型转换
无效字段处理
Guard 和 Pipe 的区别也就比较直观:
text
Guard
→ 这个请求有没有资格继续
Pipe
→ 请求带进来的参数能不能使用
10. Interceptor:为什么它能包住 Controller?
权限和参数都没问题后,请求马上就要进入 Controller。
假设我们希望统计:
text
POST /orders 执行耗时
或者统一处理响应结构、日志、缓存、超时。
这类逻辑通常不属于具体的订单业务,而是希望围绕整个 Handler 的执行过程处理。
这时就很适合使用 Interceptor(拦截器)。
Interceptor 一个很重要的特点是:
它可以包裹 Handler 的整个执行过程。
例如:
text
Interceptor Before
↓
Controller
↓
Service
↓
Interceptor After
看一个请求耗时统计的例子:
ts
@Injectable()
export class LoggingInterceptor
implements NestInterceptor {
intercept(
context: ExecutionContext,
next: CallHandler,
) {
const start = Date.now()
return next.handle().pipe(
tap(() => {
console.log(
`cost: ${Date.now() - start}ms`,
)
}),
)
}
}
请求进入时,Interceptor 先记录开始时间。
然后:
ts
next.handle()
把执行继续交给后面的 Handler。
Controller 和 Service 执行完成后,响应还会再次经过 Interceptor:
text
LoggingInterceptor Before
↓
Controller
↓
Service
↓
返回结果
↓
LoggingInterceptor After
↓
计算请求耗时
所以 Interceptor 很适合处理:
text
请求耗时
Logging
Response Transformation
Cache
Timeout
数据脱敏
为什么 NestJS Interceptor 使用 RxJS?
第一次看到 NestJS Interceptor 时,很多开发者会有一个疑问:
平时写 Node.js 业务代码,我们经常使用:
ts
async/await
Promise
为什么这里:
ts
next.handle()
返回的是:
ts
Observable
而不是 Promise?
这里涉及 RxJS。
RxJS 是一个用于处理异步数据流的 JavaScript 库。
NestJS 在 Interceptor 中使用 Observable(可观察对象)作为统一的执行结果抽象,这样 Interceptor 不只是等待一个最终值,还可以继续对整个返回过程做组合处理,例如:
- 响应转换
- 日志
- 异常处理
- 超时
- 缓存
可以先把 Promise 和 Observable 粗略区分成:
text
Promise
→ 一次异步操作
→ 最终得到一个结果
而 Observable 更像:
text
异步执行过程
↓
数据进入处理链
↓
可以继续转换 / 监听 / 处理异常
↓
结束
这里不需要把 Observable 理解成"NestJS 接口一定会返回很多次数据"。
对于 Interceptor 来说,更重要的是:
Observable 提供了一套可以继续组合和处理异步执行结果的机制。
next.handle().pipe() 到底是什么?
回到这段代码:
ts
return next.handle().pipe(
tap(() => {
console.log('request finished')
}),
)
这里有三个关键点。
next.handle()
表示继续执行后面的 Handler,并得到后续执行结果对应的 Observable:
text
Interceptor
↓
next.handle()
↓
Controller
↓
Service
↓
返回结果
pipe()
这里的:
ts
pipe()
不是前面讲的 NestJS Pipe(管道)。
它是 RxJS 用来组合数据流处理逻辑的方法。
这两个名字很像,但属于完全不同的概念:
text
NestJS Pipe
→ 请求参数校验 / 转换
RxJS pipe()
→ 组合 Observable 的处理逻辑
tap()
tap() 可以用于观察执行过程,而不改变原本的数据。
例如:
ts
tap(() => {
console.log('request finished')
})
很适合做:
text
日志
性能统计
埋点
如果需要修改真正返回给客户端的数据,则经常会看到其他 RxJS Operator(操作符),例如 map()。
实际 NestJS 项目里会大量使用 RxJS 吗?
不一定。
普通 CRUD 业务代码里,大量代码仍然是:
ts
@Get()
async findAll() {
return this.userService.findAll()
}
也就是熟悉的:
text
async / await + Promise
RxJS 更容易在下面这些地方出现:
- Interceptor
- WebSocket
- 微服务通信
- 消息流
- 其他需要组合异步数据流的场景
所以第一次学习 NestJS 时,不需要为了看懂 Interceptor 先完整学习 RxJS。
先弄清这一点就够了:
next.handle()返回 Observable,Interceptor 可以借助 RxJS 对后续 Handler 的执行结果继续加工。
多个 Interceptor 会怎么执行?
假设存在:
text
Interceptor A
Interceptor B
Controller
执行过程类似:
text
A before
↓
B before
↓
Controller
↓
B after
↓
A after
这种结构经常被称为"洋葱模型"。
也正因为这种执行方式,Interceptor 很适合处理围绕 Handler 的横切逻辑。
11. Exception Filter:如果中间出错了怎么办?
前面的流程都正常时,请求会继续得到响应。
但假设 OrderService 查询不到商品:
ts
throw new NotFoundException(
'Product not found',
)
如果这个异常没有在业务代码中处理,就会进入异常处理流程。
Exception Filter(异常过滤器)可以负责把异常转换成最终的 HTTP 错误响应:
text
OrderService
↓
throw Exception
↓
Exception Filter
↓
Error Response
常见用途包括:
text
统一错误结构
记录异常日志
业务异常映射
设置 HTTP Status Code
隐藏敏感错误信息
例如最终返回:
json
{
"code": 404,
"message": "Product not found"
}
这里需要区分一点:
成功请求不会固定经过 Exception Filter,它主要在未处理异常出现时介入。
12. Decorator、Metadata 和 ExecutionContext 是怎么串起来的?
前面的 Guard 中已经出现过:
ts
ExecutionContext
NestJS 项目里还经常能看到这样的代码:
ts
@Roles('admin')
@Post('/orders')
create() {}
为什么 Guard 能知道:
当前接口要求
admin权限?
这背后涉及几个相关概念。
Decorator(装饰器)
例如:
ts
@Roles('admin')
可以给当前 Handler 声明额外信息。
Metadata(元数据)
这些信息可以作为 Metadata 保存,例如:
text
roles = ['admin']
ExecutionContext(执行上下文)
运行时,Guard 或 Interceptor 可以通过 ExecutionContext 知道:
text
当前 Controller
当前 Handler
当前 Request
当前运行环境
例如 HTTP 场景:
ts
const request = context
.switchToHttp()
.getRequest()
Reflector(反射工具)
再通过 Reflector 读取 Handler 或 Controller 上保存的 Metadata。
整个关系可以整理成:
text
Decorator
↓
Metadata
↓
Reflector
↓
Guard / Interceptor
ExecutionContext
↓
提供当前正在执行的上下文
例如权限 Guard 最终判断的就是:
text
当前用户拥有的角色
+
当前接口要求的角色
↓
是否允许继续
这也是 Guard 比普通 Middleware 更适合做 Handler 级权限控制的重要原因。
13. Middleware、Guard、Pipe、Interceptor 到底怎么选?
学完这些组件以后,很容易产生一个问题:
日志是不是 Middleware 和 Interceptor 都能做?
可以。
真正需要判断的不是"能不能写",而是:
这段逻辑放在请求生命周期的哪个位置最合适?
可以用下面这张表快速区分:
| 组件 | 主要解决的问题 | 常见场景 |
|---|---|---|
| Middleware | 请求刚进来先做什么? | Request ID、基础日志 |
| Guard | 能不能继续? | 登录、权限 |
| Pipe | 参数能不能用? | 校验、类型转换 |
| Interceptor | 执行前后做什么? | 耗时、响应转换、缓存 |
| Exception Filter | 出错以后怎么办? | 统一异常响应 |
如果只想快速复习,可以记成:
text
Middleware
→ 请求先预处理
Guard
→ 能不能进
Pipe
→ 参数能不能用
Interceptor
→ 执行前后包一层
Exception Filter
→ 出错以后怎么收口
这些组件并不是互相替代,而是在请求生命周期的不同位置承担不同职责。
14. 用 POST /orders 把整条链串起来
前面已经把各个组件分别讲过了,这里不再重新解释,只把它们放回同一个请求中。
客户端发送:
http
POST /orders
请求体:
json
{
"productId": "A001",
"quantity": 2
}
完整流程可以整理成:
text
POST /orders
↓
RequestIdMiddleware
生成 requestId
↓
AuthGuard / RoleGuard
检查认证和权限
↓
LoggingInterceptor Before
记录开始时间
↓
ValidationPipe
校验 CreateOrderDto
↓
OrderController
接收参数并调用 OrderService
↓
OrderService
创建订单
↓
LoggingInterceptor After
记录请求耗时
↓
Response
返回订单数据
如果其中出现没有处理的异常:
text
Unhandled Exception
↓
Exception Filter
↓
Error Response
到这里,前面看起来比较零散的 NestJS 概念,就已经放回了同一条真实请求链里。
15. NestJS 面试重点快速复习
这一部分不再重新展开正文,只保留适合面试前快速复习的版本。
1. Module 主要解决什么问题?
Module 用来组织 Controller、Provider 以及模块之间的能力边界,同时影响 Provider 在不同 Module 之间的可见性。
2. imports、providers、controllers、exports 怎么区分?
text
imports
→ 引入其他 Module
controllers
→ 注册当前 Module 的 Controller
providers
→ 注册当前 Module 的 Provider
exports
→ 把 Provider 暴露给其他 Module
3. Provider 是什么?
Provider 是由 NestJS 容器管理,并且可以通过依赖注入被其他对象使用的能力。
Service 是最常见的一种 Provider。
4. 什么是 DI?
DI 把"使用依赖"和"创建依赖"分开。
类只声明自己需要什么,由 NestJS DI Container 负责查找、创建并注入对应 Provider。
5. Controller 和 Service 怎么分工?
Controller 主要负责 HTTP 层:
text
接收参数
↓
调用 Service
↓
返回结果
Service 承担主要业务逻辑。
6. 一次 HTTP 请求会经过哪些主要阶段?
可以先回答:
text
Middleware
↓
Guard
↓
Interceptor Before
↓
Pipe
↓
Controller
↓
Interceptor After
↓
Response
Controller 内部通常会继续调用 Service 等 Provider。
如果出现未处理异常,再进入 Exception Filter。
7. Middleware 和 Guard 有什么区别?
Middleware 更偏请求进入时的通用 HTTP 预处理。
Guard 更关注当前请求是否允许执行具体 Handler,并且可以结合 ExecutionContext 和 Metadata 做细粒度权限判断。
8. Guard 和 Pipe 有什么区别?
text
Guard
→ 请求有没有资格继续
Pipe
→ 参数是否合法、是否需要转换
9. Guard 和 Interceptor 有什么区别?
Guard 主要决定:
要不要继续执行。
Interceptor 主要处理:
Handler 执行前后要做什么。
10. 为什么 Interceptor 能处理执行前和执行后?
因为它通过:
ts
next.handle()
包裹后续 Handler 的执行,并可以借助 RxJS 继续处理返回的 Observable。
11. Exception Filter 什么时候执行?
请求执行过程中出现未处理异常时,Exception Filter 可以接管并生成最终错误响应。
成功请求不会固定经过它。
12. ExecutionContext 和 Reflector 有什么作用?
ExecutionContext 提供当前 Controller、Handler、Request 等运行时上下文。
Reflector 用来读取 Decorator 写入的 Metadata,经常用于权限控制等场景。
16. 总结:先把 NestJS 主干串起来
回头看整篇文章,NestJS 最重要的内容还是两条线。
第一条是应用结构:
text
Application
↓
Module
↓
Controller
↓
Provider / Service
↓
DI
第二条是请求生命周期:
text
Request
↓
Middleware
↓
Guard
↓
Interceptor
↓
Pipe
↓
Controller
↓
Interceptor
↓
Response
Unhandled Exception
↓
Exception Filter
理解 NestJS 的重点,并不是记住多少个 @xxx 装饰器。
更重要的是,当项目里出现一段逻辑时,能够判断:
它属于应用结构里的哪一层,又应该放在请求生命周期的哪个位置?
这套主干理解清楚以后,再继续学习下面这些能力会自然很多:
- Custom Provider
- Provider Scope
- Dynamic Module
- Circular Dependency /
forwardRef - Lifecycle Hooks
- ModuleRef
- Testing
- Configuration
- Authentication / Authorization
例如:
ts
ConfigModule.forRoot()
JwtModule.register()
背后会继续涉及 Dynamic Module(动态模块)。
而:
text
DEFAULT
REQUEST
TRANSIENT
则属于 Provider Scope(Provider 作用域)。
这些内容不需要一次全部掌握。
先把 Module、DI 和请求生命周期真正串起来,再往下深入,NestJS 的整体设计就不会再显得那么零散。