Skip to content

NestJS 完整技术文档 ​

本文档基于 NestJS 10.x 编写,示例统一使用 TypeScript,配合 class-validator、@nestjs/config、@nestjs/jwt、TypeORM 等官方推荐生态库。所有代码均可在真实项目中直接运行。

导航目录 ​

基础篇

进阶篇

原理篇


一、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 的对比 ​

维度ExpressKoaNestJS
定位极简 HTTP 库极简中间件框架企业级应用框架
架构约束无(自由发挥)无强约束(模块/分层/DI)
TypeScript需自行配置需自行配置原生一等公民
依赖注入无无内置 IoC 容器
异步模型回调 / Promiseasync/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:dev

2.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.json

2.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 mysql2
typescript
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;
  }
}

三个关键结论

  1. 默认单例:容器用 Map 缓存实例,全应用共享,因此 Service 天然是单例。
  2. 递归解析:依赖的依赖会被自动递归创建,构成依赖图(Dependency Graph)。
  3. 模块边界:容器按模块划分可见范围,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 容器(组装依赖) + 适配器(屏蔽底层差异)。掌握这四点,就抓住了框架的本质。