Appearance
NestJS 完整技术文档
本文档基于 NestJS 10.x 编写,示例统一使用 TypeScript,配合
class-validator、@nestjs/config、@nestjs/jwt、TypeORM等官方推荐生态库。所有代码均可在真实项目中直接运行。
导航目录
基础篇
进阶篇
- 六、中间件 Middleware
- 七、管道 Pipes 与数据校验
- 八、守卫 Guards 与 JWT 鉴权
- 九、拦截器 Interceptors
- 十、异常过滤器 Exception Filters
- 十一、数据库集成(TypeORM CRUD)
- 十二、配置管理 @nestjs/config
- 十三、动态模块与生命周期钩子
原理篇
一、NestJS 简介
核心理念
NestJS 是一个用于构建高效、可扩展的 Node.js 服务端应用的渐进式框架。它深受 Angular 启发,底层默认基于 Express(也可切换为 Fastify),核心思想是控制反转(IoC)与依赖注入(DI),并用大量装饰器(Decorator)将业务代码组织为清晰的分层架构。
1.1 为什么选择 NestJS
- 开箱即用的架构:内置模块化、依赖注入、分层设计,避免 Express 项目「怎么写都行、越写越乱」的问题。
- TypeScript 优先:完整类型支持,配合装饰器让路由、参数、校验一目了然。
- 平台无关:底层适配器可在 Express / Fastify 之间切换,业务代码无需改动。
- 生态完善:官方提供 ORM、配置、鉴权、任务调度、微服务、GraphQL、WebSocket 等模块。
- 可测试性强:DI 天然支持依赖替换,单元测试与 E2E 测试非常方便。
1.2 与 Express / Koa 的对比
| 维度 | Express | Koa | NestJS |
|---|---|---|---|
| 定位 | 极简 HTTP 库 | 极简中间件框架 | 企业级应用框架 |
| 架构约束 | 无(自由发挥) | 无 | 强约束(模块/分层/DI) |
| TypeScript | 需自行配置 | 需自行配置 | 原生一等公民 |
| 依赖注入 | 无 | 无 | 内置 IoC 容器 |
| 异步模型 | 回调 / Promise | async/await(洋葱模型) | async/await |
| 适用场景 | 小型服务、原型 | 轻量中间层 | 中大型、长期维护项目 |
一句话总结
Express/Koa 给你「自由」,NestJS 给你「规范」。团队协作、长期维护的中大型项目,NestJS 的架构约束能显著降低维护成本。
二、环境搭建
2.1 安装 CLI 并创建项目
bash
# 全局安装 NestJS CLI
npm i -g @nestjs/cli
# 创建项目(交互式选择包管理器 npm / yarn / pnpm)
nest new my-app
# 进入项目并启动开发服务(默认 3000 端口,支持热重载)
cd my-app
npm run start:dev2.2 项目目录结构解析
text
my-app/
├── src/
│ ├── main.ts # 应用入口,创建并启动 Nest 实例
│ ├── app.module.ts # 根模块,聚合所有功能模块
│ ├── app.controller.ts # 示例控制器(处理路由)
│ └── app.service.ts # 示例服务(业务逻辑)
├── test/ # E2E 测试
├── nest-cli.json # CLI 配置
├── tsconfig.json # TS 编译配置
└── package.json2.3 应用入口 main.ts
typescript
import { NestFactory } from "@nestjs/core";
import { ValidationPipe } from "@nestjs/common";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 全局路由前缀,例如 /api/users
app.setGlobalPrefix("api");
// 开启全局校验管道(后文详解)
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
// 允许跨域
app.enableCors();
await app.listen(3000);
console.log("应用已启动: http://localhost:3000/api");
}
bootstrap();2.4 常用 CLI 命令
bash
nest generate module users # 生成模块,简写 nest g mo users
nest generate controller users # 生成控制器,简写 nest g co users
nest generate service users # 生成服务,简写 nest g s users
nest g resource users # 一键生成 CRUD 资源(模块+控制器+服务+DTO)三、核心概念(Controller / Provider / Module)
NestJS 的三大基石:Controller 负责接收请求、返回响应;Provider(Service) 承载业务逻辑,通过 DI 注入;Module 组织与聚合相关能力。
3.1 Controllers 控制器
控制器通过 @Controller() 声明路由前缀,方法上的 HTTP 装饰器(@Get、@Post 等)定义具体路由。
typescript
import { Controller, Get, Param } from "@nestjs/common";
import { UsersService } from "./users.service";
@Controller("users") // 该控制器下所有路由前缀为 /users
export class UsersController {
// 通过构造函数注入 UsersService(依赖注入)
constructor(private readonly usersService: UsersService) {}
@Get() // GET /users
findAll() {
return this.usersService.findAll();
}
@Get(":id") // GET /users/1
findOne(@Param("id") id: string) {
return this.usersService.findOne(Number(id));
}
}3.2 Providers & Services 服务与依赖注入
Provider 是可被注入的类,用 @Injectable() 标记。Service 是最常见的 Provider,用于封装业务逻辑,使控制器保持「瘦」。
typescript
import { Injectable, NotFoundException } from "@nestjs/common";
export interface User {
id: number;
name: string;
}
@Injectable() // 声明为可注入的 Provider
export class UsersService {
private users: User[] = [{ id: 1, name: "Alice" }];
findAll(): User[] {
return this.users;
}
findOne(id: number): User {
const user = this.users.find((u) => u.id === id);
if (!user) throw new NotFoundException(`用户 ${id} 不存在`);
return user;
}
}依赖注入原理
Nest 内置 IoC 容器负责实例化 Provider 并管理其生命周期。当控制器构造函数声明 private readonly usersService: UsersService 时,容器会自动查找并注入对应实例,无需手动 new。默认作用域为单例(Singleton),全应用共享同一实例。
3.3 Modules 模块化组织
每个模块用 @Module() 声明,将相关的控制器与服务组织在一起,通过 imports/exports 实现模块间协作。
typescript
import { Module } from "@nestjs/common";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";
@Module({
controllers: [UsersController], // 注册控制器
providers: [UsersService], // 注册服务(模块内部可注入)
exports: [UsersService], // 导出后,其他导入本模块的模块也能注入 UsersService
})
export class UsersModule {}根模块聚合所有功能模块:
typescript
import { Module } from "@nestjs/common";
import { UsersModule } from "./users/users.module";
@Module({
imports: [UsersModule], // 导入功能模块
})
export class AppModule {}四、请求处理与 DTO
4.1 获取各类请求数据
typescript
import {
Controller,
Get,
Post,
Body,
Param,
Query,
Headers,
} from "@nestjs/common";
@Controller("users")
export class UsersController {
// 路由参数: GET /users/42
@Get(":id")
findOne(@Param("id") id: string) {
return { id };
}
// 查询参数: GET /users?page=1&size=10
@Get()
findAll(@Query("page") page = "1", @Query("size") size = "10") {
return { page: Number(page), size: Number(size) };
}
// 请求体: POST /users body: { name: 'Bob' }
@Post()
create(@Body() dto: CreateUserDto) {
return { created: dto };
}
// 请求头
@Get("me/info")
getInfo(@Headers("authorization") auth: string) {
return { auth };
}
}4.2 DTO 定义(Data Transfer Object)
DTO 是描述「请求数据形状」的类,配合 class-validator 可实现自动校验(详见管道章节)。
typescript
import { IsEmail, IsNotEmpty, IsInt, Min, IsOptional } from "class-validator";
export class CreateUserDto {
@IsNotEmpty({ message: "用户名不能为空" })
name: string;
@IsEmail({}, { message: "邮箱格式不正确" })
email: string;
@IsOptional()
@IsInt()
@Min(0)
age?: number;
}为什么用 class 而不是 interface 定义 DTO
class-validator 与 class-transformer 的装饰器需要在运行时读取元数据,interface 在编译后会被擦除,无法参与运行时校验,因此 DTO 必须用 class。
五、常用装饰器速查
| 装饰器 | 作用 | 示例 |
|---|---|---|
@Controller(prefix) | 声明控制器及路由前缀 | @Controller('users') |
@Get / @Post / @Put / @Patch / @Delete | 定义 HTTP 方法路由 | @Get(':id') |
@Param(key) | 获取路由参数 | @Param('id') id: string |
@Query(key) | 获取查询参数 | @Query('page') page: string |
@Body(key?) | 获取请求体 | @Body() dto: CreateUserDto |
@Headers(key?) | 获取请求头 | @Headers('authorization') |
@HttpCode(code) | 自定义状态码 | @HttpCode(204) |
@Injectable() | 声明可注入的 Provider | @Injectable() |
@Module({...}) | 声明模块 | @Module({ ... }) |
@UseGuards / @UsePipes / @UseInterceptors / @UseFilters | 绑定守卫/管道/拦截器/过滤器 | @UseGuards(AuthGuard) |
typescript
import { Controller, Post, Body, HttpCode } from "@nestjs/common";
@Controller("users")
export class UsersController {
@Post()
@HttpCode(201) // 显式指定返回 201
create(@Body() dto: CreateUserDto) {
return dto;
}
}六、中间件 Middleware
中间件在路由处理器之前执行,可访问 req、res 与 next,适合日志、鉴权预处理、请求耗时统计等。
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) {
const start = Date.now();
res.on("finish", () => {
console.log(
`${req.method} ${req.originalUrl} ${res.statusCode} +${
Date.now() - start
}ms`
);
});
next(); // 必须调用 next() 才能进入下一环节
}
}在模块中通过 configure 注册,并指定作用路由:
typescript
import {
Module,
NestModule,
MiddlewareConsumer,
RequestMethod,
} from "@nestjs/common";
import { LoggerMiddleware } from "./logger.middleware";
import { UsersController } from "./users/users.controller";
@Module({ controllers: [UsersController] })
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes({ path: "users", method: RequestMethod.ALL }); // 也可 forRoutes('*') 应用全局
}
}执行顺序
一次请求的执行链路为:中间件 → 守卫 → 拦截器(前) → 管道 → 路由处理器 → 拦截器(后) → 异常过滤器(若抛错)。理解这个顺序对定位问题非常关键。
七、管道 Pipes 与数据校验
管道有两大用途:**转换(transform)**输入数据、**校验(validation)**输入数据。
7.1 内置管道转换类型
typescript
import { Controller, Get, Param, ParseIntPipe } from "@nestjs/common";
@Controller("users")
export class UsersController {
@Get(":id")
// ParseIntPipe 自动把 '42' 转成数字 42,转换失败自动抛 400
findOne(@Param("id", ParseIntPipe) id: number) {
return { id, type: typeof id }; // type: number
}
}7.2 结合 class-validator 做 DTO 校验
安装依赖:
bash
npm i class-validator class-transformer配合 main.ts 中已注册的全局 ValidationPipe,请求体会自动按 DTO 装饰器校验:
typescript
import { Controller, Post, Body } from "@nestjs/common";
import { CreateUserDto } from "./dto/create-user.dto";
@Controller("users")
export class UsersController {
@Post()
// 请求体不满足 CreateUserDto 的校验规则时,自动返回 400 及错误信息
create(@Body() dto: CreateUserDto) {
return { ok: true, data: dto };
}
}ValidationPipe 常用选项
whitelist: true:自动剔除 DTO 中未声明的多余字段。forbidNonWhitelisted: true:出现多余字段时直接报错。transform: true:自动将纯 JSON 转为 DTO 类实例,并按类型转换(如字符串转数字)。
7.3 自定义管道
typescript
import { PipeTransform, Injectable, BadRequestException } from "@nestjs/common";
@Injectable()
export class ParsePositiveIntPipe implements PipeTransform<string, number> {
transform(value: string): number {
const val = parseInt(value, 10);
if (isNaN(val) || val <= 0) {
throw new BadRequestException("必须为正整数");
}
return val;
}
}八、守卫 Guards 与 JWT 鉴权
守卫决定请求是否被允许继续(返回 true/false),常用于认证与授权。
8.1 JWT 鉴权完整示例
安装依赖:
bash
npm i @nestjs/jwt @nestjs/passport passport passport-jwt认证模块与登录服务:
typescript
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
@Injectable()
export class AuthService {
constructor(private readonly jwtService: JwtService) {}
async login(username: string, password: string) {
// 实际项目应查库校验密码,此处简化
if (username !== "admin" || password !== "123456") {
throw new UnauthorizedException("用户名或密码错误");
}
const payload = { sub: 1, username };
return { access_token: await this.jwtService.signAsync(payload) };
}
}JWT 守卫:
typescript
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { Request } from "express";
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private readonly jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<Request>();
const token = request.headers.authorization?.split(" ")[1]; // Bearer xxx
if (!token) throw new UnauthorizedException("缺少令牌");
try {
// 校验通过后把解析出的用户信息挂到 request 上,供后续使用
request["user"] = await this.jwtService.verifyAsync(token, {
secret: process.env.JWT_SECRET || "dev-secret",
});
return true;
} catch {
throw new UnauthorizedException("令牌无效或已过期");
}
}
}注册模块并使用守卫:
typescript
import { Module } from "@nestjs/common";
import { JwtModule } from "@nestjs/jwt";
import { AuthService } from "./auth.service";
import { JwtAuthGuard } from "./jwt-auth.guard";
@Module({
imports: [
JwtModule.register({
secret: process.env.JWT_SECRET || "dev-secret",
signOptions: { expiresIn: "1h" },
}),
],
providers: [AuthService, JwtAuthGuard],
exports: [JwtAuthGuard, JwtModule],
})
export class AuthModule {}typescript
import { Controller, Get, UseGuards, Req } from "@nestjs/common";
import { JwtAuthGuard } from "../auth/jwt-auth.guard";
@Controller("profile")
export class ProfileController {
@UseGuards(JwtAuthGuard) // 需携带有效 JWT 才能访问
@Get()
getProfile(@Req() req: any) {
return req.user; // 守卫中挂载的用户信息
}
}九、拦截器 Interceptors
拦截器可在处理器前后插入逻辑,常用于统一响应格式、日志、缓存、耗时统计等,基于 RxJS 操作数据流。
9.1 统一响应格式拦截器
typescript
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
interface Response<T> {
code: number;
message: string;
data: T;
}
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, Response<T>>
{
intercept(
context: ExecutionContext,
next: CallHandler
): Observable<Response<T>> {
return next.handle().pipe(
// 把处理器返回值包装成统一结构
map((data) => ({ code: 0, message: "success", data }))
);
}
}9.2 耗时日志拦截器
typescript
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
Logger,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { tap } from "rxjs/operators";
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger("HTTP");
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const now = Date.now();
const req = context.switchToHttp().getRequest();
return next
.handle()
.pipe(
tap(() =>
this.logger.log(`${req.method} ${req.url} +${Date.now() - now}ms`)
)
);
}
}全局注册(在 main.ts):
typescript
app.useGlobalInterceptors(new TransformInterceptor());十、异常过滤器 Exception Filters
异常过滤器负责捕获未处理异常并统一返回结构,避免把堆栈直接暴露给客户端。
10.1 全局异常过滤器
typescript
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
Logger,
} from "@nestjs/common";
import { Request, Response } from "express";
@Catch() // 不带参数表示捕获所有异常
export class AllExceptionsFilter implements ExceptionFilter {
private readonly logger = new Logger("Exception");
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
// HttpException 使用其自带状态码,其余归类为 500
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message =
exception instanceof HttpException
? exception.getResponse()
: "服务器内部错误";
this.logger.error(`${request.method} ${request.url} -> ${status}`);
response.status(status).json({
code: status,
message,
path: request.url,
timestamp: new Date().toISOString(),
});
}
}全局注册(在 main.ts):
typescript
app.useGlobalFilters(new AllExceptionsFilter());10.2 主动抛出业务异常
typescript
import { NotFoundException, BadRequestException } from "@nestjs/common";
// 会被过滤器捕获,返回 404 及提示信息
throw new NotFoundException("资源不存在");
throw new BadRequestException("参数校验失败");十一、数据库集成(TypeORM CRUD)
以 TypeORM + MySQL 为例,实现完整的用户 CRUD。
11.1 安装与全局配置
bash
npm i @nestjs/typeorm typeorm mysql2typescript
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { UsersModule } from "./users/users.module";
@Module({
imports: [
TypeOrmModule.forRoot({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "123456",
database: "nest_demo",
autoLoadEntities: true, // 自动加载各模块注册的实体
synchronize: true, // 仅开发环境使用,会自动建表
}),
UsersModule,
],
})
export class AppModule {}11.2 定义实体 Entity
typescript
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
} from "typeorm";
@Entity("users") // 对应数据库表名
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 50 })
name: string;
@Column({ unique: true })
email: string;
@CreateDateColumn()
createdAt: Date;
}11.3 在模块中注册实体
typescript
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { User } from "./user.entity";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";
@Module({
imports: [TypeOrmModule.forFeature([User])], // 注入 User 仓库
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}11.4 Service 实现 CRUD
typescript
import { Injectable, NotFoundException } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { User } from "./user.entity";
import { CreateUserDto } from "./dto/create-user.dto";
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly userRepo: Repository<User>
) {}
create(dto: CreateUserDto) {
const user = this.userRepo.create(dto); // 构造实体
return this.userRepo.save(user); // 持久化
}
findAll() {
return this.userRepo.find();
}
async findOne(id: number) {
const user = await this.userRepo.findOne({ where: { id } });
if (!user) throw new NotFoundException(`用户 ${id} 不存在`);
return user;
}
async update(id: number, dto: Partial<CreateUserDto>) {
await this.findOne(id); // 先确认存在
await this.userRepo.update(id, dto);
return this.findOne(id);
}
async remove(id: number) {
const user = await this.findOne(id);
return this.userRepo.remove(user);
}
}11.5 Controller 暴露 RESTful 接口
typescript
import {
Controller,
Get,
Post,
Body,
Param,
Patch,
Delete,
ParseIntPipe,
} from "@nestjs/common";
import { UsersService } from "./users.service";
import { CreateUserDto } from "./dto/create-user.dto";
@Controller("users")
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}
@Get()
findAll() {
return this.usersService.findAll();
}
@Get(":id")
findOne(@Param("id", ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
@Patch(":id")
update(
@Param("id", ParseIntPipe) id: number,
@Body() dto: Partial<CreateUserDto>
) {
return this.usersService.update(id, dto);
}
@Delete(":id")
remove(@Param("id", ParseIntPipe) id: number) {
return this.usersService.remove(id);
}
}十二、配置管理 @nestjs/config
统一管理环境变量,避免硬编码敏感信息。
bash
npm i @nestjs/config.env 文件:
bash
DB_HOST=localhost
DB_PORT=3306
JWT_SECRET=my-super-secret全局加载配置:
typescript
import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { TypeOrmModule } from "@nestjs/typeorm";
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true, // 全局可用,无需在每个模块重复导入
envFilePath: ".env",
}),
// 异步配置:从 ConfigService 读取数据库参数
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
type: "mysql",
host: config.get<string>("DB_HOST"),
port: config.get<number>("DB_PORT"),
autoLoadEntities: true,
synchronize: true,
}),
}),
],
})
export class AppModule {}在服务中读取配置:
typescript
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
@Injectable()
export class TokenService {
constructor(private readonly config: ConfigService) {}
get secret(): string {
// 支持默认值
return this.config.get<string>("JWT_SECRET", "fallback-secret");
}
}十三、动态模块与生命周期钩子
13.1 动态模块
动态模块允许在导入时传入配置并返回定制化的模块,forRoot / register 都是这一模式的应用。
typescript
import { Module, DynamicModule } from "@nestjs/common";
interface DatabaseOptions {
uri: string;
}
@Module({})
export class DatabaseModule {
// 调用方: DatabaseModule.forRoot({ uri: '...' })
static forRoot(options: DatabaseOptions): DynamicModule {
return {
module: DatabaseModule,
providers: [{ provide: "DATABASE_OPTIONS", useValue: options }],
exports: ["DATABASE_OPTIONS"],
global: true, // 声明为全局模块,其他模块无需再导入
};
}
}13.2 生命周期钩子
Nest 提供一系列生命周期接口,可用于资源初始化与优雅关闭。
| 钩子 | 触发时机 |
|---|---|
onModuleInit | 模块依赖初始化完成后 |
onApplicationBootstrap | 全部模块初始化完成、应用启动后 |
onModuleDestroy | 收到终止信号,模块销毁前 |
onApplicationShutdown | 应用关闭时(可拿到信号量) |
typescript
import {
Injectable,
OnModuleInit,
OnApplicationShutdown,
Logger,
} from "@nestjs/common";
@Injectable()
export class TaskService implements OnModuleInit, OnApplicationShutdown {
private readonly logger = new Logger(TaskService.name);
private timer: NodeJS.Timeout;
onModuleInit() {
// 应用启动后开启定时任务
this.timer = setInterval(() => this.logger.log("心跳..."), 5000);
this.logger.log("TaskService 已初始化");
}
onApplicationShutdown(signal?: string) {
// 优雅关闭:清理资源
clearInterval(this.timer);
this.logger.log(`应用关闭 (${signal}),定时任务已停止`);
}
}启用优雅关闭
生命周期关闭钩子需在 main.ts 中显式开启监听:
typescript
app.enableShutdownHooks();十四、实现原理深度解析
本章目标
前面章节聚焦「如何用」,本章聚焦「为什么能这样用」。理解底层机制后,你就能明白装饰器、依赖注入、请求管线是如何协同工作的,从而在遇到疑难问题时具备排查能力。
14.1 装饰器与元数据(Metadata)——一切的地基
NestJS 的魔法核心是 TypeScript 装饰器 + reflect-metadata。装饰器本身只是一个函数,真正的关键在于它把「附加信息」写入了类的元数据。
前置条件:tsconfig.json 必须开启两个选项,否则装饰器与元数据都无法工作。
json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}emitDecoratorMetadata 会让 TS 编译器自动生成类型元数据(如构造函数参数的类型),这正是 DI 能「自动识别要注入什么」的基础。
装饰器如何存储信息:以 @Controller('users') 为例,其内部大致等价于:
typescript
import "reflect-metadata";
// 简化版 @Controller 实现
function Controller(prefix: string): ClassDecorator {
return (target) => {
// 把路由前缀写入 target 的元数据
Reflect.defineMetadata("path", prefix, target);
};
}
// 简化版 @Get 实现
function Get(path = ""): MethodDecorator {
return (target, key) => {
Reflect.defineMetadata("method", "GET", target, key);
Reflect.defineMetadata("route", path, target, key);
};
}框架启动时,会通过 Reflect.getMetadata 读回这些信息,动态拼装出真实的路由表。装饰器负责「贴标签」,框架负责「读标签并执行」。
14.2 IoC 容器与依赖注入的实现原理
依赖注入的关键问题:Nest 怎么知道 constructor(private svc: UsersService) 需要注入一个 UsersService 实例?
答案在 emitDecoratorMetadata。当类被 @Injectable() 装饰后,编译器会为其生成 design:paramtypes 元数据,记录构造函数每个参数的类型:
typescript
import "reflect-metadata";
@Injectable()
class UsersController {
constructor(private svc: UsersService) {}
}
// 编译后可通过反射拿到参数类型数组
const types = Reflect.getMetadata("design:paramtypes", UsersController);
// => [UsersService]IoC 容器的工作流程(简化模型):
typescript
class Container {
private instances = new Map();
// 解析并实例化一个类,自动处理其依赖
resolve<T>(Target: new (...args: any[]) => T): T {
if (this.instances.has(Target)) {
return this.instances.get(Target); // 单例:命中缓存直接返回
}
// 1. 读取构造函数的参数类型
const deps = Reflect.getMetadata("design:paramtypes", Target) || [];
// 2. 递归解析每个依赖(依赖也可能有自己的依赖)
const args = deps.map((dep: any) => this.resolve(dep));
// 3. 用解析好的依赖实例化目标类
const instance = new Target(...args);
this.instances.set(Target, instance);
return instance;
}
}三个关键结论
- 默认单例:容器用 Map 缓存实例,全应用共享,因此 Service 天然是单例。
- 递归解析:依赖的依赖会被自动递归创建,构成依赖图(Dependency Graph)。
- 模块边界:容器按模块划分可见范围,
exports决定一个 Provider 能否跨模块被注入。
14.3 请求生命周期的完整链路
一个 HTTP 请求进入 Nest 后,会依次穿过多层「洋葱」,理解顺序对排查问题至关重要:
text
客户端请求
│
▼
① 中间件 Middleware (最先执行,可访问原始 req/res)
│
▼
② 守卫 Guards (鉴权/授权,返回 false 则中断)
│
▼
③ 拦截器 Interceptor(前) (next.handle() 之前的逻辑)
│
▼
④ 管道 Pipes (参数转换与校验)
│
▼
⑤ 路由处理器 Controller (执行业务逻辑)
│
▼
⑥ 拦截器 Interceptor(后) (对返回值做统一包装/日志)
│
▼
响应返回客户端
※ 任意环节抛出异常 → 异常过滤器 Exception Filter 统一捕获为什么是这个顺序:
- 中间件最先执行,因为它工作在底层 HTTP 引擎(Express/Fastify)层面,尚未进入 Nest 上下文。
- 守卫在管道之前,是为了先鉴权再处理数据——未授权的请求无需浪费资源做参数校验。
- 拦截器包裹了整个处理过程(基于 RxJS 的
Observable),所以能同时在「前」和「后」插入逻辑。
14.4 底层平台适配器(Adapter)机制
Nest 能在 Express 与 Fastify 间无缝切换,靠的是适配器模式。框架定义了统一的 HttpAdapter 抽象接口,业务代码只依赖 Nest 抽象,不直接依赖 Express。
typescript
import { NestFactory } from "@nestjs/core";
import {
FastifyAdapter,
NestFastifyApplication,
} from "@nestjs/platform-fastify";
import { AppModule } from "./app.module";
async function bootstrap() {
// 只需替换适配器,业务代码零改动即可从 Express 切到 Fastify
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter()
);
await app.listen(3000);
}
bootstrap();一句话理解设计哲学
NestJS = 装饰器(收集元数据) + 反射(读取元数据) + IoC 容器(组装依赖) + 适配器(屏蔽底层差异)。掌握这四点,就抓住了框架的本质。