适用读者:拥有 Express / Koa 等 Node.js 框架经验的资深后端工程师。
目标:1 天内通读本文即可开始生产项目开发,后续按需查阅具体章节。


目录

  1. 为什么是 NestJS

  2. 核心理念:IoC / DI / AOP

  3. 项目初始化与目录结构

  4. Module(模块)—— 组织代码的基石

  5. Controller(控制器)—— 路由与请求处理

  6. Provider(提供者)—— 业务逻辑的载体

  7. DTO / 数据校验(ValidationPipe)

  8. 异常处理(ExceptionFilter)

  9. Middleware(中间件)

  10. Guard(守卫)—— 认证与鉴权

  11. Interceptor(拦截器)—— 响应转换与切面日志

  12. Pipe(管道)—— 输入变换与校验

  13. 自定义装饰器

  14. 请求生命周期总览

  15. 数据库集成(TypeORM / Prisma)

  16. 配置管理

  17. 环境变量与多环境

  18. 日志系统

  19. 文件上传与静态资源

  20. 定时任务(Schedule)

  21. 微服务支持

  22. GraphQL 集成

  23. WebSocket 集成

  24. 测试策略

  25. Swagger 文档生成

  26. 性能优化与生产部署

  27. 安全最佳实践

  28. 生产级项目目录结构

  29. 常见问题与避坑指南


1. 为什么是 NestJS

1.1 Express / Koa 的痛点

作为资深 Node.js 开发者,你一定深有体会:

  • 没有强制性的架构约束

    :Express 太自由了,一个项目 10 个人能写出 10 种风格,维护成本指数级上升。

  • 没有内置的依赖注入

    :手动 require / import 导致模块间耦合紧密,单元测试难以编写。

  • 没有开箱即用的切面能力

    :日志、鉴权、参数校验这类横切关注点,需要在每个路由里手写或通过 middleware 串联,容易遗漏。

  • 团队协作成本高

    :新人上手慢,代码审查需要花大量时间纠正架构层面的不一致。

1.2 NestJS 如何解决这些问题

NestJS 是一个受 Angular 启发的企业级 Node.js 框架,核心理念是将 OOP(面向对象)、FP(函数式)、FRP(函数响应式) 的精髓融合在一起:

特性

Spring Boot (Java)

NestJS (Node.js)

IoC 容器 / DI

装饰器驱动

AOP(Interceptor / Guard / Pipe)

ORM 集成

微服务支持

如果你熟悉 Spring Boot(Java)或 Angular,NestJS 会让你感觉非常亲切。如果你只熟悉 Express,可以理解为 NestJS 底层就是 Express(或 Fastify),但给它穿上了一套类型安全、模块化、可测试的架构铠甲

1.3 底层平台

NestJS 不是重新发明 HTTP Server,而是在 Express 或 Fastify 之上构建:

// 默认:Express
const app = await NestFactory.create(AppModule);

// 切换到 Fastify(更高性能)
import { NestFastifyApplication, FastifyAdapter } from '@nestjs/platform-fastify';
const app = await NestFactory.create<NestFastifyApplication>(
  AppModule,
  new FastifyAdapter(),
);

这意味着你仍然可以使用 Express / Fastify 生态的所有中间件,只不过用 NestJS 的方式去组织它们。


2. 核心理念:IoC / DI / AOP

2.1 IoC(控制反转)与 DI(依赖注入)

在传统的 Express 项目中:

// 典型的紧耦合写法
import { UserService } from './user.service';

app.get('/users', async (req, res) => {
  const service = new UserService();  // 手动创建,无法替换
  const users = await service.findAll();
  res.json(users);
});

NestJS 的写法:

@Controller('users')
exportclassUserController {
// NestJS 的 IoC 容器自动注入实例
constructor(private readonly userService: UserService) {}

@Get()
asyncfindAll() {
    returnthis.userService.findAll();
  }
}

好处

  • 可替换性

    :单测时可以轻松注入 mock 实例。

  • 生命周期管理

    :NestJS 管理实例的创建与销毁,你只需声明依赖关系。

  • 作用域控制

    :默认单例,也可以按需设置为请求作用域、瞬态作用域。

2.2 AOP(面向切面编程)

NestJS 在请求处理管道中提供了 5 种切面钩子,按执行顺序:

Request
  → Middleware       → 中间件(Express 兼容层)
    → Guard          → 守卫(认证 / 鉴权)
      → Interceptor (before) → 拦截器前置
        → Pipe       → 管道(参数变换 / 校验)
          → Controller → 控制器业务逻辑
          → Interceptor (after) → 拦截器后置
            → ExceptionFilter → 异常过滤器(拦截未处理异常)
  → Response

这 5 种机制让你可以优雅地分离横切关注点,后面会逐一详解。


3. 项目初始化与目录结构

3.1 CLI 脚手架

npm i -g @nestjs/cli
nest new my-project
# 选择包管理器(npm / yarn / pnpm)

生成的项目结构:

my-project/
├── src/
│   ├── app.controller.ts      # 根控制器
│   ├── app.controller.spec.ts # 单测
│   ├── app.module.ts          # 根模块
│   ├── app.service.ts         # 根服务
│   └── main.ts                # 入口文件
├── test/
│   ├── app.e2e-spec.ts        # e2e 测试
│   └── jest-e2e.json
├── nest-cli.json              # CLI 配置
├── package.json
├── tsconfig.json
└── tsconfig.build.json

3.2 核心 CLI 命令速查

命令

作用

nest g module user

生成 user 模块(默认 src/ 下)

nest g controller user

生成 user 控制器

nest g service user

生成 user 服务

nest g resource user

一键生成 CRUD 全套(module + controller + service + dto + entity)

nest g guard auth

生成守卫

nest g interceptor logging

生成拦截器

nest g pipe validation

生成管道

nest g filter http-exception

生成异常过滤器

nest g decorator roles

生成自定义装饰器

nest build

生产构建

nest start --watch

开发模式热重载

提示nest g resource 是最高效的命令,支持 REST / GraphQL / WebSocket / 微服务四种风格,交互式选择。

3.3 main.ts 详解

// src/main.ts
import { NestFactory } from'@nestjs/core';
import { ValidationPipe } from'@nestjs/common';
import { AppModule } from'./app.module';

asyncfunctionbootstrap() {
const app = awaitNestFactory.create(AppModule);

// 全局前缀
  app.setGlobalPrefix('api/v1');

// 全局管道(自动校验 DTO)
  app.useGlobalPipes(
    newValidationPipe({
      whitelist: true,       // 自动剥离 DTO 未定义的属性
      forbidNonWhitelisted: true, // 遇到未定义属性直接抛 400
      transform: true,       // 自动类型转换(字符串 "1" →数字 1)
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  );

// 全局异常过滤器
// app.useGlobalFilters(new AllExceptionsFilter());

// 开启 CORS
  app.enableCors({
    origin: ['https://example.com'],
    credentials: true,
  });

await app.listen(3000);
}
bootstrap();

4. Module(模块)—— 组织代码的基石

4.1 模块的四要素

@Module({
  imports: [],      // 导入其他模块(获取其导出的 Provider)
  controllers: [],  // 注册该模块的控制器
  providers: [],    // 注册该模块的提供者(Service、Factory、Repository 等)
  exports: [],      // 暴露给其他模块使用的 Provider
})
export class UserModule {}

4.2 模块的三种类型

1. 功能模块(Feature Module)

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UserController],
  providers: [UserService],
  exports: [UserService], // 暴露给其他模块
})
export class UserModule {}

2. 共享模块(Shared Module)

@Module({
  providers: [ConfigService, LoggerService],
  exports: [ConfigService, LoggerService],
})
export class SharedModule {}

凡是被 SharedModule.exports 导出的 Provider,任何 imports: [SharedModule] 的模块都可以注入使用。

3. 全局模块(Global Module)

@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class DatabaseModule {}

加了 @Global() 后,无需在每个模块的 imports 中引用,全局任意位置都可注入。谨慎使用,一般只用于数据库连接、配置服务等真正的全局单例

4.3 动态模块(Dynamic Module)

当模块需要运行时参数时(最常见的就是数据库连接参数),使用动态模块:

@Module({})
exportclassDatabaseModule {
staticforRoot(options: DatabaseOptions): DynamicModule {
    return {
      module: DatabaseModule,
      imports: [ConfigModule],
      providers: [
        {
          provide: 'DATABASE_OPTIONS',
          useValue: options,
        },
        DatabaseService,
      ],
      exports: [DatabaseService],
    };
  }
}

// 使用
@Module({
imports: [DatabaseModule.forRoot({ host: 'localhost', port: 5432 })],
})
exportclassAppModule {}

常见约定

  • forRoot()

    :只在根模块调用一次,初始化全局配置(如 TypeOrmModule.forRoot())。

  • forFeature()

    :在功能模块中调用,注册该模块使用的实体(如 TypeOrmModule.forFeature([User]))。

  • register()

    :类似 forRoot,但对每个调用方提供独立配置。

  • forRootAsync()

    :当配置来自异步来源(如 HTTP 请求、环境变量加载后)时使用。

4.4 模块间依赖关系

AppModule (根)
├── ConfigModule.forRoot()         → 全局配置,最先加载
├── DatabaseModule                 → 数据库连接
│   ├── TypeOrmModule.forRoot()
│   └── PrismaModule
├── CacheModule.registerAsync()    → 缓存
├── AuthModule                     → 认证
│   ├── UserModule                 → 依赖用户模块
│   └── JwtModule.registerAsync()
├── UserModule                     → 用户业务
├── OrderModule                    → 订单业务
│   └── ProductModule              → 依赖产品模块
└── CommonModule                   → 通用工具

5. Controller(控制器)—— 路由与请求处理

5.1 路由装饰器

@Controller('users')
exportclassUserController {
// GET /users
@Get()
findAll() {}

// GET /users/:id
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {}

// POST /users
@Post()
create(@Body() createUserDto: CreateUserDto) {}

// PATCH /users/:id
@Patch(':id')
update(@Param('id', ParseIntPipe) id: number, @Body() updateUserDto: UpdateUserDto) {}

// DELETE /users/:id
@Delete(':id')
@HttpCode(204)  // 自定义状态码(默认 POST=201,其他=200)
remove(@Param('id', ParseIntPipe) id: number) {}

// GET /users/:userId/orders
@Get(':userId/orders')
findUserOrders(@Param('userId') userId: string) {}

// 通配符 *
@Get('ab*cd')  // 匹配 /abcd, /abXcd, /abANYTHINGcd
wildcard() {}
}

5.2 请求参数装饰器速查

装饰器

对应 Express 对象

说明

@Request()

 / @Req()

req

完整请求对象

@Response()

 / @Res()

res

完整响应对象(使用后需手动 res.send

@Body() req.body

请求体

@Param(key?) req.params

路由参数

@Query(key?) req.query

查询参数

@Headers(key?) req.headers

请求头

@Ip() req.ip

客户端 IP

@HostParam() req.hosts

主机参数(域名路由)

@Session() req.session

Session(需 express-session)

@UploadedFile() req.file

上传的单个文件

@UploadedFiles() req.files

上传的多个文件

注意:一旦在方法参数中注入 @Res(),NestJS 会认为你将手动处理响应,return 值不再自动发送,也不会触发 Interceptor。除非必要,尽量只使用路由参数装饰器。

5.3 状态码与响应头

@Post()
@HttpCode(201)  // 默认 POST 就是 201,此处只是演示
@Header('X-Custom-Header', 'custom-value')
@Header('Cache-Control', 'no-store')
create(@Body() dto: CreateUserDto) {
  return this.userService.create(dto);
}

5.4 子路由控制器

// 父路由
@Controller('users') → /users
// 子路由:通过 @Controller 不加前缀的方式挂载

使用 @Module 中将多个控制器绑定到同一前缀更简洁:

// 假设需要 /users/profiles 和 /users/settings
@Controller('users/profiles')  // 直接写完整路径
export class UserProfileController {}

5.5 响应方式对比

// 方式一:标准模式(推荐)—— NestJS 自动处理
@Get()
findAll() {
returnthis.userService.findAll();  // 自动→ res.json(data)
}

// 方式二:RxJS 流
@Get()
@StreamableFile()
stream() {
returnnewStreamableFile(readStream);  // 文件流
}

// 方式三:手动响应(需要 @Res())
@Get()
findAll(@Res() res: Response) {
  res.status(200).json({ data: [] });
// 注意:这里不能用 return,必须调用 res.send/json
}

6. Provider(提供者)—— 业务逻辑的载体

6.1 基础用法

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
    private readonly configService: ConfigService,
  ) {}

  async findAll(): Promise<User[]> {
    return this.userRepository.find();
  }
}

任何用 @Injectable() 装饰的类,都可以被 NestJS 的 IoC 容器管理,并在构造函数中声明依赖,容器会自动注入。

6.2 三种注入 Token 的类型

// 1. 类 Token(最常用)—— Token 即类的构造函数
@Injectable()
classUserService {}
// providers: [UserService]  ←等价于 { provide: UserService, useClass: UserService }

// 2. 字符串 Token ——用于注入普通值
@Module({
providers: [
    { provide: 'DATABASE_CONFIG', useValue: { host: 'localhost' } },
  ],
})
// 注入:constructor(@Inject('DATABASE_CONFIG') config: DatabaseConfig) {}

// 3. Symbol Token ——避免命名冲突
exportconstDB_CONFIG = Symbol('DB_CONFIG');
// providers: [{ provide: DB_CONFIG, useValue: { host: 'localhost' } }]
// 注入:constructor(@Inject(DB_CONFIG) config: DatabaseConfig) {}

6.3 Provider 的四种注册模式

// useClass:每次注入创建指定类的实例(最常用)
{ provide: UserService, useClass: UserService }

// useValue:注入固定值(常量、mock 对象、外部库实例)
{ provide: 'APP_NAME', useValue: 'MyApp' }

// useFactory:动态创建(可依赖其他 Provider)
{
provide: 'REDIS_CLIENT',
useFactory: (config: ConfigService) => {
    returnnewRedis(config.get('REDIS_URL'));
  },
inject: [ConfigService],  // 注入依赖给工厂函数
}

// useExisting:别名,多个 Token 指向同一个实例
{ provide: 'UserRepoAlias', useExisting: UserRepository }

6.4 作用域(Scope)

// 默认:DEFAULT(单例)——全局共享一个实例
@Injectable()  // scope: Scope.DEFAULT

// 请求作用域:每个 HTTP 请求创建一个新实例
@Injectable({ scope: Scope.REQUEST })
exportclassRequestContextService {
privateuserId: string;
setUserId(id: string) { this.userId = id; }
}

// 瞬态作用域:每次注入创建新实例
@Injectable({ scope: Scope.TRANSIENT })

性能提醒:请求作用域会显著增加内存和 GC 压力。如果只是需要在请求维度传递数据(如当前用户 ID),优先使用 AsyncLocalStorage(Node.js 内置)或 @nestjs-cls/ClsModule,而非请求作用域。


7. DTO / 数据校验(ValidationPipe)

7.1 DTO 定义

// src/user/dto/create-user.dto.ts
import { IsEmail, IsString, MinLength, MaxLength, IsOptional, IsEnum } from'class-validator';
import { ApiProperty, ApiPropertyOptional } from'@nestjs/swagger';

exportenumUserRole {
ADMIN = 'admin',
USER = 'user',
GUEST = 'guest',
}

exportclassCreateUserDto {
@ApiProperty({ description: '邮箱', example: 'user@example.com' })
@IsEmail({}, { message: '邮箱格式不正确' })
email: string;

@ApiProperty({ description: '密码', minLength: 8, maxLength: 32 })
@IsString()
@MinLength(8)
@MaxLength(32)
password: string;

@ApiPropertyOptional({ description: '昵称', maxLength: 50, default: '' })
@IsOptional()
@IsString()
@MaxLength(50)
nickname?: string;

@ApiPropertyOptional({ description: '角色', enum: UserRole, default: UserRole.USER })
@IsOptional()
@IsEnum(UserRole)
role?: UserRole;
}

7.2 全局开启校验

// main.ts
app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,               // 自动移除 DTO 未定义字段
    forbidNonWhitelisted: true,    // 检测到未定义字段抛 400
    transform: true,               // 自动类型转换
    enableImplicitConversion: true,// 隐式转换(如 "1" → 1)
    stopAtFirstError: true,        // 遇到第一个错误就停止(提升性能)
    validateCustomDecorators: true,// 校验自定义装饰器
  }),
);

7.3 校验错误响应格式

默认格式:

{
  "statusCode": 400,
  "message": ["email must be an email", "password must be longer than or equal to 8 characters"],
  "error": "Bad Request"
}

如果你希望统一响应格式(如 { code: 400, message: '...', data: null }),可以使用 exceptionFactory 自定义:

new ValidationPipe({
exceptionFactory: (errors) => {
    const messages = errors.map((e) => ({
      field: e.property,
      constraints: e.constraints,
    }));
    returnnewBadRequestException({
      code: 400,
      message: '参数校验失败',
      errors: messages,
    });
  },
});

7.4 部分更新(PartialType)

// 更新 DTO 继承 Create DTO,所有字段变可选
import { PartialType } from '@nestjs/swagger';  // 支持 Swagger
// 或 import { PartialType } from '@nestjs/mapped-types';  // 不支持 Swagger

export class UpdateUserDto extends PartialType(CreateUserDto) {}

7.5 复杂校验场景

// 确认密码
import { Matches, ValidateIf } from'class-validator';

exportclassRegisterDto {
@IsString()
@MinLength(8)
password: string;

@IsString()
@ValidateIf((o) => o.password)  // 只在 password 存在时才校验
@Matches((o) => o.password, {
    message: '两次密码不一致',
  })
confirmPassword: string;
}

// 条件必填
@ValidateIf((o) => o.role === UserRole.ADMIN)
@IsString()
departmentId?: string;  // 仅管理员必填

8. 异常处理(ExceptionFilter)

8.1 内置异常速览

throw newBadRequestException('参数错误');
thrownewUnauthorizedException('未登录');
thrownewForbiddenException('权限不足');
thrownewNotFoundException('资源不存在');
thrownewConflictException('资源冲突');
thrownewInternalServerErrorException('服务器内部错误');
thrownewServiceUnavailableException('服务不可用');
thrownewGatewayTimeoutException('网关超时');

// 也可以传对象
thrownewBadRequestException({
code: 400,
message: '邮箱已被注册',
field: 'email',
});

8.2 全局异常过滤器(生产推荐)

// src/common/filters/all-exceptions.filter.ts
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
Logger,
} from'@nestjs/common';
import { Request, Response } from'express';

@Catch()
exportclassAllExceptionsFilterimplementsExceptionFilter {
privatereadonly logger = newLogger(AllExceptionsFilter.name);

catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();

    letstatus: number;
    letmessage: string;
    letdetail: any;

    if (exception instanceofHttpException) {
      status = exception.getStatus();
      const res = exception.getResponse();
      message = typeof res === 'string' ? res : (res asany).message || '请求失败';
      detail = typeof res === 'object' ? res : null;
    } else {
      status = HttpStatus.INTERNAL_SERVER_ERROR;
      message = '服务器内部错误';
      // 记录未预期的错误
      this.logger.error(
        `未捕获异常: ${(exception as Error).message}`,
        (exception asError).stack,
        `${request.method} ${request.url}`,
      );
    }

    response.status(status).json({
      code: status,
      message,
      data: null,
      timestamp: newDate().toISOString(),
      path: request.url,
      ...(process.env.NODE_ENV === 'development' && { detail }),
    });
  }
}

在 main.ts 中注册:

app.useGlobalFilters(new AllExceptionsFilter());

9. Middleware(中间件)

9.1 基本用法

Middleaware 是 NestJS 中最接近 Express 中间件的概念。它是一个实现了 NestMiddleware 接口的类:

// src/common/middleware/logger.middleware.ts
import { Injectable, NestMiddleware, Logger } from'@nestjs/common';
import { Request, Response, NextFunction } from'express';

@Injectable()
exportclassLoggerMiddlewareimplementsNestMiddleware {
privatereadonly logger = newLogger(LoggerMiddleware.name);

use(req: Request, res: Response, next: NextFunction) {
    const start = Date.now();
    const { method, originalUrl } = req;

    res.on('finish', () => {
      const duration = Date.now() - start;
      const { statusCode } = res;
      this.logger.log(`${method} ${originalUrl} ${statusCode} ${duration}ms`);
    });

    next();
  }
}

9.2 注册 Middleware

// 在 Module 中注册
exportclassAppModuleimplementsNestModule {
configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware, HelmetMiddleware)
      .exclude(
        { path: 'health', method: RequestMethod.GET },
        { path: 'metrics', method: RequestMethod.GET },
      )
      .forRoutes(UserController);  // 仅对 UserController 生效

    // 也可以对整个模块生效
    // .forRoutes('users'); // 字符串路由
    // .forRoutes('*');     // 全局
  }
}

9.3 Middleware vs Interceptor

维度

Middleware

Interceptor

执行时机

路由匹配之前

Guard 之后、Pipe 之前

访问请求体

✅ 可以

❌ 不能(Pipe 执行之前)

访问响应体

❌ 需要 hack

✅ 天然支持

使用 Express API

✅ 直接使用

❌ 需通过 ExecutionContext

适用场景

请求日志、CORS、Body Parser

响应格式统一、缓存


10. Guard(守卫)—— 认证与鉴权

10.1 基础 Guard

import { Injectable, CanActivate, ExecutionContext } from'@nestjs/common';
import { Observable } from'rxjs';

@Injectable()
exportclassAuthGuardimplementsCanActivate {
canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    const token = request.headers.authorization;
    if (!token) {
      returnfalse;  // 返回 false → 403 Forbidden
    }
    // 验证 token...
    returntrue;
  }
}

10.2 JWT 认证实战

npm install @nestjs/jwt @nestjs/passport passport passport-jwt
npm install -D @types/passport-jwt
// src/auth/auth.module.ts
@Module({
imports: [
    JwtModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        secret: config.get('JWT_SECRET'),
        signOptions: { expiresIn: config.get('JWT_EXPIRES_IN', '7d') },
      }),
    }),
    PassportModule,
  ],
providers: [AuthService, JwtStrategy],
exports: [AuthService],
})
exportclassAuthModule {}

JWT 策略:

// src/auth/jwt.strategy.ts
@Injectable()
exportclassJwtStrategyextendsPassportStrategy(Strategy) {
constructor(config: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: config.get('JWT_SECRET'),
    });
  }

asyncvalidate(payload: JwtPayload): Promise<JwtPayload> {
    // 此处可查询数据库验证用户是否存在,附加值到 req.user
    return { userId: payload.sub, email: payload.email, role: payload.role };
  }
}

使用 AuthGuard:

@Controller('users')
export class UserController {
  @UseGuards(AuthGuard('jwt'))
  @Get('profile')
  getProfile(@Req() req: Request) {
    return req.user;  // JwtStrategy.validate 的返回值
  }
}

10.3 角色鉴权 Guard

// 自定义装饰器
exportconstROLES_KEY = 'roles';
exportconstRoles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);

// 角色 Guard
@Injectable()
exportclassRolesGuardimplementsCanActivate {
constructor(private reflector: Reflector) {}

canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (!requiredRoles) returntrue;

    const { user } = context.switchToHttp().getRequest();
    return requiredRoles.some((role) => user.role === role);
  }
}

// 使用
@Controller('admin')
@UseGuards(AuthGuard('jwt'), RolesGuard)
@Roles('admin')
exportclassAdminController {
@Get('dashboard')
dashboard() {}
}

11. Interceptor(拦截器)—— 响应转换与切面日志

11.1 统一响应格式拦截器(生产必备)

// src/common/interceptors/transform.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from'@nestjs/common';
import { Observable } from'rxjs';
import { map } from'rxjs/operators';

exportinterfaceApiResponse<T> {
code: number;
message: string;
data: T;
}

@Injectable()
exportclassTransformInterceptor<T>
implementsNestInterceptor<T, ApiResponse<T>>
{
intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<ApiResponse<T>> {
    return next.handle().pipe(
      map((data) => ({
        code: context.switchToHttp().getResponse().statusCode,
        message: 'ok',
        data: data ?? null,
      })),
    );
  }
}

全局注册:

// main.ts
app.useGlobalInterceptors(new TransformInterceptor());

这样,所有返回都会被包裹成:

{
  "code": 200,
  "message": "ok",
  "data": { ... }
}

11.2 请求耗时拦截器

@Injectable()
exportclassTimeoutInterceptorimplementsNestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const start = Date.now();
    return next.handle().pipe(
      tap(() => {
        const duration = Date.now() - start;
        if (duration > 1000) {
          Logger.warn(
            `慢请求: ${context.switchToHttp().getRequest().url} - ${duration}ms`,
          );
        }
      }),
      timeout(30000),  // 30 秒超时自动取消
    );
  }
}

11.3 缓存拦截器

import { CACHE_MANAGER } from'@nestjs/cache-manager';
import { Cache } from'cache-manager';

@Injectable()
exportclassCacheInterceptorimplementsNestInterceptor {
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}

asyncintercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
    const request = context.switchToHttp().getRequest();
    const cacheKey = `cache:${request.url}`;

    const cached = awaitthis.cacheManager.get(cacheKey);
    if (cached) returnof(cached);

    return next.handle().pipe(
      tap(async (data) => {
        awaitthis.cacheManager.set(cacheKey, data, 60_000); // 60s TTL
      }),
    );
  }
}

12. Pipe(管道)—— 输入变换与校验

12.1 内置 Pipe

// 类型转换
ParseIntPipe      // "1" → 1
ParseFloatPipe    // "1.23" → 1.23
ParseBoolPipe     // "true" → true
ParseArrayPipe    // "1,2,3" → [1,2,3]
ParseUUIDPipe     // 校验 UUID 格式
ParseEnumPipe     // 校验枚举值

// 默认值
DefaultValuePipe  // 未传值时使用默认值

// 校验
ValidationPipe    // 基于 class-validator 的 DTO 校验

12.2 自定义 Pipe

import { PipeTransform, Injectable, BadRequestException } from'@nestjs/common';

@Injectable()
exportclassTrimPipeimplementsPipeTransform {
transform(value: any) {
    if (value && typeof value === 'string') {
      return value.trim();
    }
    if (value && typeof value === 'object') {
      Object.keys(value).forEach((key) => {
        if (typeof value[key] === 'string') {
          value[key] = value[key].trim();
        }
      });
    }
    return value;
  }
}

配合 ValidationPipe 使用:

// 可以组合使用
@Post()
create(@Body(new TrimPipe()) createUserDto: CreateUserDto) {}

13. 自定义装饰器

13.1 常用自定义装饰器

// src/common/decorators/current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from'@nestjs/common';

exportconstCurrentUser = createParamDecorator(
(dataProp: string | undefined, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const user = request.user;
    return dataProp ? user?.[dataProp] : user;
  },
);

// 使用
@Get('profile')
getProfile(@CurrentUser() user: JwtPayload) {}
getProfile(@CurrentUser('email') email: string) {}

13.2 公共路由装饰器

export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

// 使用(跳过 JWT 认证)
@Public()
@Get('health')
healthCheck() {}

然后在 JWT Guard 中:

@Injectable()
exportclassJwtAuthGuardextendsAuthGuard('jwt') {
constructor(private reflector: Reflector) {
    super();
  }

canActivate(context: ExecutionContext) {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (isPublic) returntrue;
    returnsuper.canActivate(context);
  }
}

14. 请求生命周期总览

一张图理解完整的请求处理流程:

┌──────────────────────────────────────────────────────────────────┐
│                        HTTP 请求到达                              │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│ 1. Middleware(中间件)                                           │
│    - Express / Fastify 兼容层                                     │
│    - 全局中间件 → 模块中间件 → 路由中间件                           │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│ 2. Guards(守卫)                                                │
│    - 全局守卫 → 控制器守卫 → 路由守卫                             │
│    - 决定请求是否继续                                             │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│ 3. Interceptors(拦截器)—— 前置逻辑                              │
│    - 全局拦截器 → 控制器拦截器 → 路由拦截器                        │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│ 4. Pipes(管道)                                                 │
│    - 全局管道 → 控制器管道 → 路由管道 → 参数管道                  │
│    - 类型转换与参数校验                                           │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│ 5. Controller(控制器)—— 业务处理                               │
│    - 调用 Service 层                                              │
│    - 返回数据                                                     │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│ 6. Interceptors(拦截器)—— 后置逻辑                              │
│    - RxJS Observable pipe 中的 map/tap 等                         │
│    - 路由拦截器 → 控制器拦截器 → 全局拦截器(反向)                │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│ 7. ExceptionFilters(异常过滤器)                                 │
│    - 仅当抛出异常时                                               │
│    - 路由过滤器 → 控制器过滤器 → 全局过滤器                        │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│                         HTTP 响应返回                             │
└──────────────────────────────────────────────────────────────────┘

执行顺序口诀:M → G → I(前) → P → C → I(后) → E(如有异常)
优先级口诀:路由级 > 控制器级 > 全局级(细粒度的覆盖粗粒度的)


15. 数据库集成(TypeORM / Prisma)

15.1 TypeORM

npm install @nestjs/typeorm typeorm pg
// app.module.ts
@Module({
imports: [
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        type: 'postgres',
        host: config.get('DB_HOST'),
        port: config.get<number>('DB_PORT'),
        username: config.get('DB_USER'),
        password: config.get('DB_PASS'),
        database: config.get('DB_NAME'),
        entities: [__dirname + '/**/*.entity{.ts,.js}'],
        synchronize: config.get('NODE_ENV') !== 'production', // 生产禁用!!!
        logging: config.get('NODE_ENV') === 'development',
        // 连接池配置
        extra: {
          max: 20,
          idleTimeoutMillis: 30000,
        },
      }),
    }),
  ],
})

Entity 定义:

// src/user/user.entity.ts
@Entity('users')
exportclassUser {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column({ unique: true })
email: string;

@Column({ select: false }) // 默认查询不返回此字段
password: string;

@Column({ nullable: true })
nickname: string;

@Column({ type: 'enum', enum: UserRole, default: UserRole.USER })
role: UserRole;

@CreateDateColumn()
createdAt: Date;

@UpdateDateColumn()
updatedAt: Date;

// 虚拟字段(非数据库列)
@Exclude() // class-transformer 序列化时排除
getsafeProfile() {
    return { id: this.id, email: this.email, nickname: this.nickname };
  }
}

Repository 模式:

// user.service.ts
@Injectable()
exportclassUserService {
constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

asyncfindByEmail(email: string): Promise<User | null> {
    returnthis.userRepository.findOne({
      where: { email },
      select: ['id', 'email', 'password', 'role'], // 手动指定字段
    });
  }

asyncfindWithPagination(query: PaginationQuery) {
    const [items, total] = awaitthis.userRepository.findAndCount({
      skip: (query.page - 1) * query.pageSize,
      take: query.pageSize,
      order: { createdAt: 'DESC' },
    });
    return { items, total, page: query.page, pageSize: query.pageSize };
  }
}

15.2 Prisma(推荐新项目)

npm install prisma @prisma/client
npx prisma init
// prisma/schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        String   @id @default(uuid())
  email     String   @unique
  password  String
  nickname  String?
  role      Role     @default(USER)
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")
  posts     Post[]

  @@map("users")
}

model Post {
  id        String   @id @default(uuid())
  title     String
  content   String?
  author    User     @relation(fields: [authorId], references: [id])
  authorId  String   @map("author_id")
  createdAt DateTime @default(now())

  @@map("posts")
}

enum Role {
  ADMIN
  USER
  GUEST
}

NestJS Prisma 服务:

// src/prisma/prisma.service.ts
@Injectable()
exportclassPrismaServiceextendsPrismaClientimplementsOnModuleInit, OnModuleDestroy {
constructor() {
    super({
      log: process.env.NODE_ENV === 'dev'
        ? ['query', 'info', 'warn', 'error']
        : ['error'],
    });
  }

asynconModuleInit() {
    awaitthis.$connect();
  }

asynconModuleDestroy() {
    awaitthis.$disconnect();
  }
}
// src/user/user.service.ts
@Injectable()
exportclassUserService {
constructor(private readonly prisma: PrismaService) {}

asyncfindByEmail(email: string) {
    returnthis.prisma.user.findUnique({ where: { email } });
  }

asyncfindWithPagination(query: PaginationQuery) {
    const [items, total] = awaitPromise.all([
      this.prisma.user.findMany({
        skip: (query.page - 1) * query.pageSize,
        take: query.pageSize,
        orderBy: { createdAt: 'desc' },
      }),
      this.prisma.user.count(),
    ]);
    return { items, total, page: query.page, pageSize: query.pageSize };
  }
}

15.3 TypeORM vs Prisma 选择建议

维度

TypeORM

Prisma

类型安全

一般

强(自动生成类型)

迁移管理

migrations CLI

prisma migrate dev

查询灵活性

QueryBuilder 灵活但字符串拼接

类型安全链式 API

复杂关系

支持良好

支持良好

性能

优秀

较新版本大幅改善

生态成熟度

更成熟

增长迅速

2025 年推荐

老项目维护

新项目首选

16. 配置管理

npm install @nestjs/config

16.1 配置验证(生产必备)

// src/config/configuration.ts
import { plainToInstance } from'class-transformer';
import { IsEnum, IsNumber, IsString, Max, Min, validateSync } from'class-validator';

enumEnvironment {
Development = 'development',
Production = 'production',
Staging = 'staging',
}

classEnvironmentVariables {
@IsEnum(Environment)
NODE_ENV: Environment;

@IsNumber()
@Min(1)
@Max(65535)
PORT: number;

@IsString()
DB_HOST: string;

@IsNumber()
@Min(1)
@Max(65535)
DB_PORT: number;

@IsString()
DB_USER: string;

@IsString()
DB_PASS: string;

@IsString()
DB_NAME: string;

@IsString()
JWT_SECRET: string;

@IsString()
JWT_EXPIRES_IN: string;
}

exportfunctionvalidate(config: Record<string, unknown>) {
const validatedConfig = plainToInstance(EnvironmentVariables, config, {
    enableImplicitConversion: true,
  });

const errors = validateSync(validatedConfig, {
    skipMissingProperties: false,
  });

if (errors.length > 0) {
    thrownewError(`Config validation error: ${errors.toString()}`);
  }

return validatedConfig;
}
// app.module.ts
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: ['.env.development.local', '.env.development', '.env'],
      validate,
      expandVariables: true,  // 支持 ${VAR_NAME} 引用
    }),
  ],
})

16.2 命名空间配置

// src/config/database.config.ts
import { registerAs } from'@nestjs/config';

exportdefaultregisterAs('database', () => ({
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10) || 5432,
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
}));

// 注入
constructor(@Inject(databaseConfig.KEY) private dbConfig: ConfigType<typeof databaseConfig>) {}

17. 环境变量与多环境

.env                    # 所有环境默认值
.env.development        # 开发环境覆盖
.env.development.local  # 本地开发覆盖(加入 .gitignore)
.env.production         # 生产环境
.env.staging            # 预发环境

.env 文件示例:

# .env.development
NODE_ENV=development
PORT=3000
DB_HOST=localhost
DB_PORT=5432
DB_USER=dev
DB_PASS=dev123
DB_NAME=myapp_dev
JWT_SECRET=dev-secret-do-not-use-in-prod
JWT_EXPIRES_IN=7d
// main.ts 中根据环境禁用同步和日志
if (configService.get('NODE_ENV') === 'production') {
  app.useLogger(['error', 'warn', 'log']);  // 生产环境只记录重要日志
} else {
  app.useLogger(['debug', 'log', 'warn', 'error', 'verbose']);
}

18. 日志系统

18.1 使用 NestJS 内置 Logger

import { Logger } from'@nestjs/common';

@Injectable()
exportclassUserService {
privatereadonly logger = newLogger(UserService.name);  // 自动带上类名

asynccreateUser(dto: CreateUserDto) {
    this.logger.log(`Creating user: ${dto.email}`);
    try {
      // ...
    } catch (error) {
      this.logger.error(`Failed to create user: ${dto.email}`, error.stack);
      throw error;
    }
  }
}

18.2 集成 Winston(生产推荐)

npm install nest-winston winston
// src/logger/winston.logger.ts
import * as winston from'winston';
import'winston-daily-rotate-file';

exportconst winstonLogger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
    winston.format.errors({ stack: true }),
    winston.format.json(),
  ),
transports: [
    // 控制台输出
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.printf(({ timestamp, level, message, context, ...meta }) => {
          return`${timestamp} [${level}] [${context || 'Application'}]: ${message} ${Object.keys(meta).length ? JSON.stringify(meta) : ''}`;
        }),
      ),
    }),
    // 按天滚动日志文件
    new winston.transports.DailyRotateFile({
      filename: 'logs/application-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      maxSize: '20m',
      maxFiles: '14d',
    }),
    // 错误日志单独存储
    new winston.transports.DailyRotateFile({
      filename: 'logs/error-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      level: 'error',
      maxFiles: '30d',
    }),
  ],
});
// main.ts
import { WinstonModule } from 'nest-winston';
import { winstonLogger } from './logger/winston.logger';

const app = await NestFactory.create(AppModule, {
  logger: WinstonModule.createLogger({
    instance: winstonLogger,
  }),
});

18.3 请求链路追踪

使用 @nestjs-cls/ClsModule + AsyncLocalStorage 实现请求级上下文(如 traceId):

npm install nestjs-cls
// app.module.ts
@Module({
imports: [
    ClsModule.forRoot({
      global: true,
      middleware: { mount: true },
    }),
  ],
})

// 中间件生成 traceId
@Injectable()
exportclassTraceMiddlewareimplementsNestMiddleware {
constructor(private readonly cls: ClsService) {}
use(req: Request, res: Response, next: NextFunction) {
    const traceId = req.headers['x-trace-id'] asstring || randomUUID();
    this.cls.set('traceId', traceId);
    res.setHeader('x-trace-id', traceId);
    next();
  }
}

19. 文件上传与静态资源

19.1 文件上传

// 单文件上传
@Post('upload')
@UseInterceptors(FileInterceptor('file', {
storage: diskStorage({
    destination: './uploads',
    filename: (req, file, cb) => {
      const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
      const ext = path.extname(file.originalname);
      cb(null, `${uniqueSuffix}${ext}`);
    },
  }),
limits: { fileSize: 5 * 1024 * 1024 },     // 5MB
fileFilter: (req, file, cb) => {
    if (!file.mimetype.match(/^image\/(jpeg|png|gif)$/)) {
      returncb(newBadRequestException('仅支持图片格式'), false);
    }
    cb(null, true);
  },
}))
uploadFile(@UploadedFile() file: Express.Multer.File) {
return { url: `/static/${file.filename}` };
}

// 多文件上传
@Post('uploads')
@UseInterceptors(FilesInterceptor('files', 5))  // 最多 5 个
uploadFiles(@UploadedFiles() files: Express.Multer.File[]) {}

19.2 静态资源托管

// main.ts
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';

const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useStaticAssets(join(__dirname, '..', 'uploads'), {
  prefix: '/static/',
  maxAge: '30d',
});

19.3 对接 S3 / OSS(生产推荐)

@Injectable()
exportclassS3Service {
privates3: S3Client;

constructor() {
    this.s3 = newS3Client({
      region: process.env.AWS_REGION,
      credentials: {
        accessKeyId: process.env.AWS_ACCESS_KEY,
        secretAccessKey: process.env.AWS_SECRET_KEY,
      },
    });
  }

asyncuploadFile(file: Express.Multer.File, key: string) {
    const command = newPutObjectCommand({
      Bucket: process.env.S3_BUCKET,
      Key: key,
      Body: file.buffer,
      ContentType: file.mimetype,
    });
    awaitthis.s3.send(command);
    return`https://${process.env.S3_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${key}`;
  }
}

20. 定时任务(Schedule)

npm install @nestjs/schedule
@Module({
  imports: [ScheduleModule.forRoot()],
})
export class AppModule {}
@Injectable()
exportclassTasksService {
privatereadonly logger = newLogger(TasksService.name);

// Cron 表达式
@Cron('0 0 3 * * *', { name: 'cleanExpiredTokens' })  // 每天凌晨 3 点
handleCleanTokens() {
    this.logger.log('清理过期 Token...');
  }

// 固定间隔
@Interval(60000)  // 每 60 秒
handleCacheClean() {
    this.logger.debug('清理缓存...');
  }

// 延迟执行
@Timeout(5000)
handleOnStartup() {
    this.logger.log('服务启动 5 秒后执行');
  }

// 动态控制(通过 SchedulerRegistry)
constructor(private schedulerRegistry: SchedulerRegistry) {}

enableCronJob(name: string) {
    const job = this.schedulerRegistry.getCronJob(name);
    job.start();
  }
}

Cron 速查

* * * * * *
| | | | | |
| | | | | └── 星期几 (0-7,0和7都代表周日)
| | | | └──── 月份 (1-12)
| | | └────── 日期 (1-31)
| | └──────── 小时 (0-23)
| └────────── 分钟 (0-59)
└──────────── 秒 (0-59,可选)

21. 微服务支持

NestJS 内置了对多种传输层的微服务支持:

// main.ts
// 运行 HTTP 服务的同时,也启动微服务监听
const app = awaitNestFactory.create(AppModule);

// TCP 传输
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.TCP,
options: { host: '0.0.0.0', port: 8877 },
});

// Redis 传输
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.REDIS,
options: { url: 'redis://localhost:6379' },
});

// RabbitMQ 传输
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.RMQ,
options: {
    urls: ['amqp://localhost:5672'],
    queue: 'main_queue',
    queueOptions: { durable: true },
  },
});

// Kafka
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.KAFKA,
options: {
    client: { brokers: ['localhost:9092'] },
    consumer: { groupId: 'main-consumer' },
  },
});

await app.startAllMicroservices();
await app.listen(3000);

消息模式:

@Controller()
exportclassMathController {
// 请求-响应模式
@MessagePattern({ cmd: 'sum' })
sum(data: number[]): number {
    return data.reduce((a, b) => a + b, 0);
  }

// 事件模式(无返回值)
@EventPattern('user_created')
handleUserCreated(data: any) {
    console.log('用户已创建:', data);
  }
}

22. GraphQL 集成

npm install @nestjs/graphql @nestjs/apollo @apollo/server graphql

22.1 代码优先模式(推荐)

// app.module.ts
@Module({
imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
      sortSchema: true,
      playground: process.env.NODE_ENV !== 'production',
    }),
  ],
})

// user.resolver.ts
@Resolver(() =>User)
exportclassUserResolver {
constructor(private readonly userService: UserService) {}

@Query(() => [User], { name: 'users' })
findAll() {
    returnthis.userService.findAll();
  }

@Query(() =>User, { name: 'user', nullable: true })
findOne(@Args('id', { type: () => String }) id: string) {
    returnthis.userService.findOne(id);
  }

@Mutation(() =>User)
createUser(@Args('input') input: CreateUserInput) {
    returnthis.userService.create(input);
  }

@ResolveField(() => [Post])
asyncposts(@Parent() user: User) {
    returnthis.postService.findByUserId(user.id);
  }
}

23. WebSocket 集成

npm install @nestjs/websockets @nestjs/platform-socket.io
@WebSocketGateway({
namespace: 'chat',
cors: { origin: '*' },
})
exportclassChatGatewayimplementsOnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server: Server;

private onlineUsers = newMap<string, string>();  // socketId → userId

handleConnection(client: Socket) {
    console.log(`客户端连接: ${client.id}`);
  }

handleDisconnect(client: Socket) {
    this.onlineUsers.delete(client.id);
  }

@SubscribeMessage('join')
handleJoin(client: Socket, userId: string) {
    this.onlineUsers.set(client.id, userId);
    client.join(`user:${userId}`);
    // 广播上线消息
    this.server.emit('userOnline', { userId });
  }

@SubscribeMessage('message')
handleMessage(client: Socket, payload: { to: string; content: string }) {
    this.server.to(`user:${payload.to}`).emit('message', {
      from: this.onlineUsers.get(client.id),
      content: payload.content,
      timestamp: newDate(),
    });
  }

// 从其他 Service 推送消息
@SubscribeMessage('systemNotice')
sendToUser(userId: string, message: string) {
    this.server.to(`user:${userId}`).emit('message', {
      from: 'system',
      content: message,
    });
  }
}

24. 测试策略

24.1 单元测试

// user.service.spec.ts
describe('UserService', () => {
letservice: UserService;
letrepository: Repository<User>;

beforeEach(async () => {
    constmodule = awaitTest.createTestingModule({
      providers: [
        UserService,
        {
          provide: getRepositoryToken(User),
          useValue: {
            find: jest.fn(),
            findOne: jest.fn(),
            save: jest.fn(),
            create: jest.fn(),
            delete: jest.fn(),
          },
        },
      ],
    }).compile();

    service = module.get<UserService>(UserService);
    repository = module.get<Repository<User>>(getRepositoryToken(User));
  });

it('should find all users', async () => {
    const mockUsers = [{ id: '1', email: 'test@test.com' }];
    jest.spyOn(repository, 'find').mockResolvedValue(mockUsers asUser[]);

    const result = await service.findAll();
    expect(result).toEqual(mockUsers);
    expect(repository.find).toHaveBeenCalled();
  });
});

24.2 Controller 测试

// user.controller.spec.ts
describe('UserController', () => {
letcontroller: UserController;
letservice: UserService;

beforeEach(async () => {
    constmodule = awaitTest.createTestingModule({
      controllers: [UserController],
      providers: [
        {
          provide: UserService,
          useValue: {
            findAll: jest.fn(),
            create: jest.fn(),
          },
        },
      ],
    }).compile();

    controller = module.get<UserController>(UserController);
    service = module.get<UserService>(UserService);
  });

it('should return paginated users', async () => {
    const result = { items: [], total: 0, page: 1, pageSize: 10 };
    jest.spyOn(service, 'findAll').mockResolvedValue(result);

    expect(await controller.findAll({ page: 1, pageSize: 10 })).toBe(result);
  });
});

24.3 E2E 测试

// test/app.e2e-spec.ts
describe('AppController (e2e)', () => {
letapp: INestApplication;

beforeAll(async () => {
    const moduleFixture = awaitTest.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    app.useGlobalPipes(newValidationPipe({ whitelist: true }));
    await app.init();
  });

it('/users (POST)', () => {
    returnrequest(app.getHttpServer())
      .post('/users')
      .send({ email: 'test@test.com', password: 'password123' })
      .expect(201)
      .expect((res) => {
        expect(res.body.data.email).toBe('test@test.com');
        expect(res.body.data.password).toBeUndefined();  // 密码不应返回
      });
  });

afterAll(async () => {
    await app.close();
  });
});

测试原则

  • 单测覆盖 Service 核心业务逻辑。

  • Controller 测试:验证参数校验和返回值结构。

  • E2E 测试:验证完整链路,但不要测所有分支(太慢),只测关键流程。

  • 集成测试用 Testcontainers 或本地 Docker,不要 mock 数据库。


25. Swagger 文档生成

npm install @nestjs/swagger
// main.ts
const config = newDocumentBuilder()
  .setTitle('My API')
  .setDescription('项目 API 文档')
  .setVersion('1.0')
  .addBearerAuth()       // JWT Bearer Token
  .addTag('users', '用户管理')
  .addTag('auth', '认证')
  .build();

constdocument = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api-docs', app, document, {
swaggerOptions: {
    persistAuthorization: true,  // 刷新后保留 Token
    docExpansion: 'none',        // 默认折叠所有接口
  },
});

访问 http://localhost:3000/api-docs 即可看到 Swagger UI。


26. 性能优化与生产部署

26.1 编译优化

// tsconfig.build.json
{
"extends":"./tsconfig.json",
"compilerOptions":{
    "sourceMap":false,          // 生产禁用 sourceMap
    "declaration":false         // 生产禁用类型声明
},
"exclude":["node_modules","test","dist","**/*spec.ts"]
}

26.2 集群模式(PM2)

npm install -g pm2
// ecosystem.config.json
{
"apps":[
    {
      "name":"my-app",
      "script":"./dist/main.js",
      "instances":"max",           // 根据 CPU 核数
      "exec_mode":"cluster",
      "env":{
        "NODE_ENV":"production",
        "PORT":3000
      },
      "max_memory_restart":"512M",// 内存超限自动重启
      "error_file":"./logs/pm2-error.log",
      "out_file":"./logs/pm2-out.log",
      "merge_logs":true,
      "kill_timeout":10000,        // 优雅关闭超时
      "listen_timeout":10000
    }
]
}
pm2 start ecosystem.config.json
pm2 save           # 保存进程列表
pm2 startup        # 开机自启

26.3 优雅关闭

// main.ts
asyncfunctionbootstrap() {
const app = awaitNestFactory.create(AppModule);
await app.listen(3000);

// 监听 SIGTERM / SIGINT
const signals = ['SIGTERM', 'SIGINT'];
for (const signal of signals) {
    process.on(signal, async () => {
      console.log(`收到 ${signal} 信号,开始优雅关闭...`);
      await app.close();
      process.exit(0);
    });
  }
}

26.4 使用 Fastify(更高吞吐量)

// main.ts
import { NestFastifyApplication, FastifyAdapter } from'@nestjs/platform-fastify';
import fastifyCompress from'@fastify/compress';
import fastifyHelmet from'@fastify/helmet';
import fastifyCors from'@fastify/cors';

const app = awaitNestFactory.create<NestFastifyApplication>(
AppModule,
newFastifyAdapter({ logger: false }),
);

await app.register(fastifyCompress);
await app.register(fastifyHelmet);
await app.register(fastifyCors, { origin: ['https://example.com'], credentials: true });
await app.listen(3000, '0.0.0.0');

性能对比:Fastify 在纯吞吐量场景下比 Express 快约 2-3 倍,建议对性能敏感的新项目优先选择 Fastify。

26.5 数据库连接池

// TypeORM
extra: {
max: 20,                  // 最大连接数
connectionTimeoutMillis: 5000,
idleTimeoutMillis: 30000,
statement_timeout: 30000, // 单条 SQL 超时
}

// Prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
// Prisma 连接池配置通过 connection_limit 参数
// postgresql://user:pass@host:5432/db?connection_limit=20
}

27. 安全最佳实践

27.1 Helmet(HTTP 安全头)

npm install helmet
// main.ts
import helmet from 'helmet';
app.use(helmet());

27.2 速率限制

npm install @nestjs/throttler
@Module({
imports: [
    ThrottlerModule.forRoot({
      throttlers: [
        {
          ttl: 60000,  // 时间窗口 60 秒
          limit: 100,  // 每个客户端最多 100 次请求
        },
      ],
    }),
  ],
})

// 对单个路由限制
@UseGuards(ThrottlerGuard)
@Throttle({ default: { limit: 3, ttl: 60000 } })  // 每分钟 3 次
@Post('send-code')
sendVerificationCode() {}

27.3 CSRF 保护

npm install csurf
import * as csurf from 'csurf';
app.use(csurf({ cookie: true }));

27.4 输入安全

// 防范 XSS
import * as xss from 'xss';

// 可以在 TrimPipe 中加入 XSS 过滤
transform(value: any) {
  if (typeof value === 'string') return xss(value.trim());
  return value;
}

27.5 密码安全

import * as bcrypt from'bcrypt';

constSALT_ROUNDS = 12;

asynchashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}

asyncverifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}

// 使用 timingSafeEqual 防止时序攻击(仅用于 API Key 对比等场景)
import { timingSafeEqual } from'crypto';

27.6 安全清单

  •  所有敏感配置通过环境变量注入,不硬编码

  •  JWT Secret 长度 ≥ 256 位

  •  密码使用 bcrypt(cost ≥ 12)哈希

  •  生产环境关闭 synchronize: true

  •  生产环境关闭 GraphQL playground

  •  生产环境关闭 Swagger 或加 IP 白名单 / Basic Auth

  •  数据库连接走 SSL/TLS

  •  敏感操作(删除 / 导出)需要二次确认或 2FA

  •  所有外部输入经过 ValidationPipe 校验

  •  响应中不暴露内部错误堆栈

  •  日志中不打印密码、Token、身份证号等敏感信息


28. 生产级项目目录结构

my-project/
├── .github/
│   └── workflows/                # CI/CD
│       └── ci.yml
├── prisma/
│   ├── schema.prisma
│   └── migrations/
├── src/
│   ├── common/                   # 通用模块
│   │   ├── constants/            # 常量
│   │   ├── decorators/           # 自定义装饰器 (current-user, public, roles)
│   │   ├── filters/              # 全局异常过滤器
│   │   ├── guards/               # 全局守卫
│   │   ├── interceptors/         # 全局拦截器 (transform, timeout, cache)
│   │   ├── middleware/           # 全局中间件 (logger, traceId)
│   │   ├── pipes/                # 全局管道
│   │   └── utils/                # 工具函数
│   ├── config/                   # 配置模块
│   │   ├── configuration.ts
│   │   ├── database.config.ts
│   │   └── jwt.config.ts
│   ├── prisma/                   # Prisma 封装
│   │   ├── prisma.module.ts
│   │   └── prisma.service.ts
│   ├── modules/                  # 业务模块
│   │   ├── auth/
│   │   │   ├── auth.module.ts
│   │   │   ├── auth.controller.ts
│   │   │   ├── auth.service.ts
│   │   │   ├── auth.service.spec.ts
│   │   │   ├── jwt.strategy.ts
│   │   │   └── dto/
│   │   │       ├── login.dto.ts
│   │   │       └── register.dto.ts
│   │   ├── user/
│   │   │   ├── user.module.ts
│   │   │   ├── user.controller.ts
│   │   │   ├── user.service.ts
│   │   │   ├── user.repository.ts
│   │   │   ├── user.service.spec.ts
│   │   │   └── dto/
│   │   │       ├── create-user.dto.ts
│   │   │       ├── update-user.dto.ts
│   │   │       └── query-user.dto.ts
│   │   └── order/
│   │       └── ...
│   ├── app.module.ts
│   └── main.ts
├── test/
│   ├── app.e2e-spec.ts
│   └── jest-e2e.json
├── uploads/                      # 本地文件上传目录
├── logs/                         # 日志目录
├── .env.development
├── .env.production
├── .env.example                  # 环境变量模板(提交到 git)
├── .gitignore
├── .eslintrc.js
├── .prettierrc
├── ecosystem.config.json         # PM2
├── docker-compose.yml            # 本地开发环境
├── Dockerfile
├── package.json
├── tsconfig.json
└── tsconfig.build.json

29. 常见问题与避坑指南

29.1 循环依赖

问题:A 模块依赖 B,B 又依赖 A,导致模块初始化失败。

解决方案

// 使用 forwardRef
@Module({
imports: [forwardRef(() =>BModule)],
exports: [AService],
})
exportclassAModule {}

@Injectable()
exportclassAService {
constructor(
    @Inject(forwardRef(() => BService))
    private bService: BService,
  ) {}
}

更好的方案:重构代码,抽出共享依赖到 CommonModule,从根源消除循环依赖。

29.2 N+1 查询

问题:查询用户列表时,每个用户又单独查询其关联订单。

TypeORM 解决方案

this.userRepository.find({
  relations: { orders: true },  // 一次 JOIN 查询
});

Prisma 解决方案

this.prisma.user.findMany({
  include: { posts: true },
});

29.3 事务处理

TypeORM

await this.entityManager.transaction(async (manager) => {
  const user = manager.create(User, dto);
  await manager.save(user);
  await manager.save(Profile, { ...profileDto, userId: user.id });
});

Prisma

await this.prisma.$transaction(async (tx) => {
  const user = await tx.user.create({ data: dto });
  await tx.profile.create({ data: { ...profileDto, userId: user.id } });
});

29.4 Module 初始化顺序

NestJS 按模块拓扑排序初始化,如果配置模块用了 forRootAsync,确保它在 imports 数组中位于依赖它的模块之前。isGlobal: true 的模块可以在任意位置声明。

29.5 生产环境 Source Map 泄露

# .gitignore
dist/
*.js.map

生产 npm run build 时确保 tsconfig.build.json 中 "sourceMap": false

29.6 内存泄漏

常见原因:

  • EventEmitter 监听未移除

    :确保 addListener 后有对应的 removeListener

  • 闭包捕获大对象

    :小心在闭包中引用不需要的大对象。

  • 缓存无限增长

    :为所有缓存设置 TTL 和最大容量。

  • 请求作用域滥用

    :避免在非必要场景使用 Scope.REQUEST

29.7 请求/响应体过大

// main.ts - Express
app.use(express.json({ limit: '10mb' }));

// main.ts - Fastify
const app = await NestFactory.create<NestFastifyApplication>(
  AppModule,
  new FastifyAdapter({ bodyLimit: 10485760 }),  // 10MB
);

29.8 同步阻塞

// ❌错误:同步加密阻塞事件循环
const hash = crypto.createHash('sha256').update(data).digest('hex');

// ✅正确:使用异步版本或 Worker Threads
const hash = await bcrypt.hash(password, 12);

附录 A:推荐依赖清单

{
  "dependencies":{
    "@nestjs/common":"^10.x",
    "@nestjs/core":"^10.x",
    "@nestjs/config":"^3.x",
    "@nestjs/jwt":"^10.x",
    "@nestjs/passport":"^10.x",
    "@nestjs/swagger":"^7.x",
    "@nestjs/throttler":"^5.x",
    "@nestjs/schedule":"^4.x",
    "@nestjs/websockets":"^10.x",
    "class-validator":"^0.14.x",
    "class-transformer":"^0.5.x",
    "bcrypt":"^5.x",
    "passport":"^0.7.x",
    "passport-jwt":"^4.x",
    "prisma":"^5.x",
    "@prisma/client":"^5.x",
    "winston":"^3.x",
    "nest-winston":"^1.x",
    "nestjs-cls":"^4.x",
    "helmet":"^7.x",
    "xss":"^1.x"
},
"devDependencies":{
    "@nestjs/cli":"^10.x",
    "@nestjs/testing":"^10.x",
    "jest":"^29.x",
    "ts-jest":"^29.x",
    "supertest":"^6.x",
    "@types/jest":"^29.x",
    "@types/express":"^4.x"
}
}

附录 B:学习路线图

第一阶段(1-2天):核心概念
├── Module / Controller / Provider / DI
├── DTO + ValidationPipe
├── ExceptionFilter
└── Interceptor(统一响应格式)

第二阶段(2-3天):进阶能力
├── Guard(JWT 认证 + 角色鉴权)
├── Middleware vs Interceptor 区别
├── Swagger 文档
├── 配置管理与环境变量
└── 自定义装饰器

第三阶段(3-5天):实战能力
├── TypeORM / Prisma CRUD + 分页 + 事务
├── 文件上传(本地 + S3)
├── 日志系统(Winston + 链路追踪)
├── 定时任务
└── 测试(单元 + E2E)

第四阶段(按需):扩展生态
├── 微服务(TCP / Redis / Kafka)
├── GraphQL
├── WebSocket
├── 缓存策略(Redis)
└── 消息队列集成

30. 场景篇 - NestJS 适用业务场景

30.1 为什么选择 NestJS 的场景判断

NestJS 是完美的选择,如果你面临:

  1. 团队规模 ≥ 3 人,且会持续增长

    • 自由的 Express 容易导致代码风格混乱

    • NestJS 的架构约束能统一团队规范

    • 新人上手成本大幅降低

  2. 预期业务会持续迭代、功能会快速增多

    • 模块系统天然支持业务领域划分

    • 松耦合设计让改动影响范围可控

    • 优秀的测试能力保障迭代质量

  3. 需要构建企业级 B 端系统或中台

    • 天然支持多环境配置

    • 完整的安全体系(认证、鉴权)

    • 符合企业级系统稳定性要求

  4. 项目会有长时间维护周期(≥ 1 年)

    • 代码组织方式可扩展性强

    • 模块化设计便于后续演进

    • 不依赖个人英雄主义

NestJS 可能不是最优选择,如果你面临:

  1. 纯单页面静态托管或简单 CRUD

    • Express / Fastify 足够轻量

    • 引入 Nest 反而增加复杂度

  2. 极致性能优先且团队技术栈偏好原生 Node

    • Fastify 原生会更轻量(相差约 15%)

    • 但 NestJS + Fastify 已经非常快

  3. 项目周期极短,属于一次性工具

    • 快速上线优先,架构约束可以后置

30.2 典型企业级业务场景

场景 1:电商平台 / 交易系统

核心需求:
- 多租户/多店铺架构
- 订单流程复杂
- 高并发支付回调处理
- 实时库存同步

NestJS 优势:
- 模块化架构适合按业务领域拆分(用户、订单、商品、支付)
- Interceptor + Schedule 完美处理异步订单状态更新
- Guard + Roles 天然支持多角色权限(买家、卖家、运营、财务)
- 集成消息队列(Bull)处理并发回调和库存同步

场景 2:企业内部管理系统(ERP/CRM)

核心需求:
- 复杂的 RBAC 权限体系
- 大量表单与流程审批
- 数据导出与报表生成
- 与多个第三方系统集成

NestJS 优势:
- Guard + Reflector + 自定义装饰器实现灵活 RBAC
- DTO + ValidationPipe 完美处理表单校验
- TypeORM/Prisma 关系映射适合复杂业务数据
- Swagger 自动文档减少沟通成本

场景 3:实时协作系统(协同文档/在线白板)

核心需求:
- WebSocket 实时通信
- 事件驱动架构
- 多人状态一致性
- 离线冲突处理

NestJS 优势:
- @WebSocketGateway 开箱即用
- 与 RxJS 完美结合处理事件流
- 模块系统便于协作逻辑与业务逻辑分离
- 可集成 Redis 做跨实例通信

场景 4:微服务集群

核心需求:
- 服务间 RPC 通信
- 服务发现与负载均衡
- 统一的配置与错误处理
- 链路追踪

NestJS 优势:
- @nestjs/microservices 内置多种传输协议(TCP、Redis、NATS、RabbitMQ)
- 统一的架构风格降低服务间切换成本
- 与 Config、Logger、Tracing 等模块无缝配合

30.3 从 Express 迁移到 Nest 的场景分析

迁移路径选择:

  1. 渐进式迁移(推荐大项目)
    • 保持现有 Express 服务运行

    • 新功能使用 NestJS 开发

    • 逐步将旧代码迁移

  2. 重写式迁移(适合新项目或小项目)
    • 利用 NestJS CLI 快速生成骨架

    • 利用现有 Express 知识迁移业务逻辑

    • 重构时统一代码规范


31. 架构篇 - 分层架构与设计模式

31.1 NestJS 经典分层架构详解

┌─────────────────────────────────────────────────────┐
│                   Presentation Layer (展现层)          │
│            Controller (接收 HTTP 请求)                │
├─────────────────────────────────────────────────────┤
│                   Application Layer (应用层)          │
│  ⇓  Guards (认证鉴权)                                  │
│  ⇓  Interceptors (请求/响应拦截)                      │
│  ⇓  Pipes (参数校验/转换)                            │
├─────────────────────────────────────────────────────┤
│                  Domain Layer (领域层)               │
│            Service (业务逻辑核心)                     │
│            Entity (领域实体)                          │
│            Value Objects (值对象)                     │
│            Domain Events (领域事件)                   │
├─────────────────────────────────────────────────────┤
│                Infrastructure Layer (基础设施层)    │
│            Repository (数据访问)                     │
│            External APIs (外部集成)                  │
│            Message Broker (消息队列)                 │
│            Caching (缓存)                           │
└─────────────────────────────────────────────────────┘

31.2 DDD (领域驱动设计) 在 NestJS 中的落地

按业务领域划分模块,而不是按技术层划分:

// ❌ 技术层划分(不推荐)
src/
  controllers/
  services/
  repositories/
  entities/

// ✅ 业务领域划分(推荐)
src/modules/
  user/
    user.module.ts
    user.controller.ts
    user.service.ts
    user.repository.ts
    entities/
      user.entity.ts
  order/
    order.module.ts
    order.controller.ts
    order.service.ts

DDD 核心概念在 Nest 中的映射:

DDD 概念

NestJS 对应

聚合根

Entity + Repository

领域事件

Interceptor + EventEmitter

应用服务

Service + Decorators

仓储

Repository(TypeORM/Prisma)

值对象

Class-Validator + Class-Transformer

31.3 设计模式实战

1. 单例模式(Singleton)

// Nest 默认所有 Provider 都是单例
@Injectable()
export class SingletonService {
  constructor() {}
}

2. 工厂模式(Factory)

@Injectable()
exportclassPaymentServiceFactory {
privatestaticreadonly paymentProviders = {
    alipay: AlipayPaymentService,
    wechat: WechatPaymentService,
  };

staticcreate(providerName: string): PaymentService {
    constProvider = this.paymentProviders[providerName];
    if (!Provider) thrownewNotFoundException('支付方式不存在');
    returnnewProvider();
  }
}

3. 策略模式(Strategy)

// 策略接口
interfacePaymentStrategy {
pay(amount: number): Promise<string>;
}

// 具体策略
@Injectable()
classAlipayStrategyimplementsPaymentStrategy {
asyncpay(amount: number) { /* ... */ }
}

@Injectable()
classWechatStrategyimplementsPaymentStrategy {
asyncpay(amount: number) { /* ... */ }
}

// 上下文
@Injectable()
exportclassPaymentContext {
constructor(
    @Inject('ALIPAY_STRATEGY') private alipay: PaymentStrategy,
    @Inject('WECHAT_STRATEGY') private wechat: PaymentStrategy,
  ) {}

getStrategy(provider: string) {
    return provider === 'alipay' ? this.alipay : this.wechat;
  }
}

4. 观察者模式(Observer)

import { OnEvent } from'@nestjs/event-emitter';

@Injectable()
exportclassUserCreatedListener {
@OnEvent('user.created')
handleUserCreatedEvent(event: UserCreatedEvent) {
    console.log('处理用户创建:', event.userId);
  }
}

// 发送事件
exportclassUserService {
constructor(private readonly eventEmitter: EventEmitter2) {}

asynccreateUser(data: CreateUserDto) {
    const user = awaitthis.userRepo.save(data);
    this.eventEmitter.emit('user.created', { userId: user.id });
    return user;
  }
}

32. 实战篇 - 完整生产项目实战

32.1 实战项目:电商订单系统

项目架构规划:

my-ecommerce/
├── prisma/
│   └── schema.prisma
├── src/
│   ├── common/
│   │   ├── decorators/
│   │   │   ├── public.decorator.ts
│   │   │   ├── current-user.decorator.ts
│   │   ├── filters/
│   │   │   └── all-exceptions.filter.ts
│   │   ├── interceptors/
│   │   │   ├── transform.interceptor.ts
│   │   │   └── logging.interceptor.ts
│   │   ├── guards/
│   │   │   ├── jwt-auth.guard.ts
│   │   │   └── roles.guard.ts
│   ├── config/
│   │   └── configuration.ts
│   ├── prisma/
│   │   └── prisma.service.ts
│   ├── modules/
│   │   ├── auth/
│   │   │   ├── dto/
│   │   │   ├── auth.controller.ts
│   │   │   ├── auth.service.ts
│   │   │   ├── jwt.strategy.ts
│   │   │   └── auth.module.ts
│   │   ├── user/
│   │   │   ├── dto/
│   │   │   ├── user.controller.ts
│   │   │   ├── user.service.ts
│   │   │   ├── user.repository.ts
│   │   │   └── user.module.ts
│   │   ├── product/
│   │   │   ├── dto/
│   │   │   ├── product.controller.ts
│   │   │   ├── product.service.ts
│   │   │   └── product.module.ts
│   │   ├── order/
│   │   │   ├── dto/
│   │   │   ├── order.controller.ts
│   │   │   ├── order.service.ts
│   │   │   ├── order.repository.ts
│   │   │   ├── events/
│   │   │   │   ├── order-created.event.ts
│   │   │   │   └── order-paid.event.ts
│   │   │   └── order.module.ts
│   │   ├── payment/
│   │   │   ├── strategies/
│   │   │   │   ├── alipay.strategy.ts
│   │   │   │   └── wechat.strategy.ts
│   │   │   ├── payment.service.ts
│   │   │   └── payment.module.ts
│   │   └── notification/
│   │       └── notification.module.ts
│   └── main.ts
└── package.json

32.2 订单服务核心代码实战

1. 领域事件:

// src/modules/order/events/order-created.event.ts
export class OrderCreatedEvent {
  constructor(
    public readonly orderId: string,
    public readonly userId: string,
    public readonly amount: number,
  ) {}
}

2. 订单服务:

@Injectable()
exportclassOrderService {
constructor(
    private readonly prisma: PrismaService,
    private readonly productService: ProductService,
    private readonly eventEmitter: EventEmitter2,
  ) {}

asynccreateOrder(userId: string, dto: CreateOrderDto) {
    returnawaitthis.prisma.$transaction(async (tx) => {
      // 1. 锁定库存并扣减
      for (const item of dto.items) {
        awaitthis.productService.deductStock(tx, item.productId, item.quantity);
      }
      
      // 2. 创建订单
      const order = await tx.order.create({
        data: {
          userId,
          status: 'pending',
          amount: dto.amount,
          items: {
            create: dto.items.map(item => ({
              productId: item.productId,
              quantity: item.quantity,
              price: item.price,
            })),
          },
        },
        include: { items: true },
      });
      
      // 3. 发送领域事件
      this.eventEmitter.emit('order.created', newOrderCreatedEvent(order.id, userId, dto.amount));
      
      return order;
    });
  }

asyncprocessPayment(orderId: string, dto: ProcessPaymentDto) {
    const order = awaitthis.prisma.order.findUniqueOrThrow({
      where: { id: orderId },
    });
    
    if (order.status !== 'pending') thrownewBadRequestException('订单状态错误');
    
    // 使用策略模式选择支付方式
    const paymentStrategy = this.paymentContext.getStrategy(dto.provider);
    const paymentResult = await paymentStrategy.pay(order.amount);
    
    const updatedOrder = awaitthis.prisma.order.update({
      where: { id: orderId },
      data: { status: 'paid' },
    });
    
    // 发送支付成功事件
    this.eventEmitter.emit('order.paid', { orderId, amount: order.amount });
    
    return updatedOrder;
  }
}

3. 订单事件监听器:

@Injectable()
exportclassOrderListener {
constructor(
    private readonly notificationService: NotificationService,
    private readonly logger: Logger,
  ) {}

@OnEvent('order.created')
handleOrderCreated(event: OrderCreatedEvent) {
    this.logger.log(`订单创建: ${event.orderId}`);
    this.notificationService.sendEmail(
      event.userId,
      '订单已创建',
      `您的订单 ${event.orderId} 已创建,金额 ${event.amount}`,
    );
  }

@OnEvent('order.paid')
handleOrderPaid(event: OrderPaidEvent) {
    this.logger.log(`订单已支付: ${event.orderId}`);
    this.notificationService.sendPush(event.orderId, '您的订单已支付成功');
  }
}

33. 案例篇 - 企业级项目案例

33.1 案例 1:大型 SaaS 多租户系统

背景:
某企业级 HR SaaS,服务 1000+ 企业客户,每日请求量 500万+,数据量级千万级。

架构选型:

技术栈:
- 后端框架:NestJS + Fastify
- 数据库:PostgreSQL (分租户)
- 缓存:Redis Cluster
- 消息队列:RabbitMQ
- 网关:Kong
- 监控:Prometheus + Grafana

部署方式:
- Docker Compose 本地
- K8s 生产

核心技术亮点:

  1. 多租户架构
// 租户中间件
@Injectable()
exportclassTenantMiddlewareimplementsNestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
    const tenantId = req.headers['x-tenant-id'] asstring;
    if (!tenantId) thrownewUnauthorizedException('租户 ID 缺失');
    
    // 设置当前租户上下文
    this.cls.set('tenantId', tenantId);
    next();
  }
}

// 租户感知的 Repository
@Injectable()
exportclassTenantAwareRepository<T> {
constructor(
    private readonly prisma: PrismaService,
    private readonly cls: ClsService,
  ) {}

findAll(tableName: string) {
    const tenantId = this.cls.get('tenantId');
    returnthis.prisma[tableName].findMany({ where: { tenantId } });
  }
}
  1. 高并发库存扣减
// 基于 Redis 分布式锁
import { Lock } from'redlock';

@Injectable()
exportclassInventoryService {
asyncdeductStock(productId: string, quantity: number) {
    const lock = awaitthis.redlock.lock(`lock:stock:${productId}`, 5000);
    
    try {
      const product = awaitthis.prisma.product.findUniqueOrThrow({
        where: { id: productId },
      });
      
      if (product.stock < quantity) {
        thrownewBadRequestException('库存不足');
      }
      
      returnawaitthis.prisma.product.update({
        where: { id: productId },
        data: { stock: { decrement: quantity } },
      });
    } finally {
      await lock.unlock();
    }
  }
}

性能结果:

  • 单实例 QPS 从 Express 的 1500 提升到 5000

  • P99 延迟从 500ms 降到 120ms

  • 内存占用稳定在 250MB / pod


34. 性能篇 - 性能优化深度解析

34.1 性能基准测试:Nest vs Express vs Fastify

测试环境:

  • CPU: 4核

  • 内存: 8GB

  • Node.js: v20

结果:

场景

Express

Nest(Express)

Nest(Fastify)

简单 hello world (req/s)

15,000

12,000

18,000

数据库查询 (req/s)

3,000

2,800

3,200

内存占用 (MB)

80

120

100

启动时间 (s)

0.2

0.8

0.7

结论:

  • Nest 相比原生 Express 有约 15-20% 的性能损耗,但远低于架构收益

  • Nest + Fastify 几乎能追平原生 Express 性能

  • 数据库查询场景下,性能主要受限于 IO,框架影响不大

34.2 优化技巧 1:Fastify 适配器替代 Express

// main.ts
import { NestFactory } from'@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from'@nestjs/platform-fastify';
import compression from'@fastify/compress';
import helmet from'@fastify/helmet';

asyncfunctionbootstrap() {
const app = awaitNestFactory.create<NestFastifyApplication>(
    AppModule,
    newFastifyAdapter({ logger: false }),
  );

// 注册 Fastify 插件
await app.register(compression);
await app.register(helmet);

await app.listen(3000, '0.0.0.0');
}
bootstrap();

34.3 优化技巧 2:连接池配置

// 数据库连接池
@Module({
imports: [
    TypeOrmModule.forRoot({
      extra: {
        max: 20,
        min: 5,
        idleTimeoutMillis: 30000,
        connectionTimeoutMillis: 2000,
        statement_timeout: 30000,
      },
    }),
  ],
})

// Redis 连接池
import { createClient } from'redis';

const redisClient = createClient({
socket: {
    reconnectStrategy: retries =>Math.min(retries * 50, 500),
  },
});

34.4 优化技巧 3:缓存策略

1. 本地内存缓存(L1 缓存)

// 使用 node-cache
importNodeCachefrom'node-cache';

@Injectable()
exportclassLocalCacheService {
private cache = newNodeCache({ stdTTL: 60 });

  get<T>(key: string): T | undefined {
    returnthis.cache.get(key);
  }

  set<T>(key: string, value: T, ttl?: number): void {
    this.cache.set(key, value, ttl);
  }
}

2. Redis 分布式缓存(L2 缓存)

import { CACHE_MANAGER } from'@nestjs/cache-manager';
import { Cache } from'cache-manager';

@Injectable()
exportclassUserService {
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}

asyncgetUserById(id: string) {
    const cacheKey = `user:${id}`;
    
    const cachedUser = awaitthis.cacheManager.get<User>(cacheKey);
    if (cachedUser) return cachedUser;
    
    const user = awaitthis.prisma.user.findUniqueOrThrow({ where: { id } });
    awaitthis.cacheManager.set(cacheKey, user, 3600); // 1h TTL
    
    return user;
  }
}

3. 缓存更新策略(Cache-Aside)

async updateUser(id: string, dto: UpdateUserDto) {
const user = awaitthis.prisma.user.update({
    where: { id },
    data: dto,
  });

// 失效缓存
awaitthis.cacheManager.del(`user:${id}`);
return user;
}

34.5 优化技巧 4:数据库查询优化

避免 N+1 查询

// ❌ N+1
const orders = await prisma.order.findMany({});
for (const order of orders) {
  order.user = await prisma.user.findUnique({ 
    where: { id: order.userId } 
  });
}

// ✅ JOIN 查询
const orders = await prisma.order.findMany({
  include: { user: true },
});

使用索引查询

model Order {
  id        String   @id @default(uuid())
  userId    String
  status    String
  createdAt DateTime @default(now())

  @@index([userId])
  @@index([status])
  @@index([createdAt])
}

34.6 优化技巧 5:流式响应

import { createReadStream } from'fs';
import { createInterface } from'readline';

@Controller('large-file')
exportclassLargeFileController {
@Get('stream')
@Sse()
asyncstreamLargeFile() {
    const fileStream = createReadStream('large-file.csv');
    const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
    
    returnnewObservable(subscriber => {
      rl.on('line', line => {
        subscriber.next({ data: line });
      });
      rl.on('close', () => subscriber.complete());
    });
  }
}

35. 监控与运维篇

35.1 日志结构化与日志收集

// 使用 Winston 结构化日志
import * as winston from'winston';

exportconst winstonLogger = winston.createLogger({
format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json(),
  ),
transports: [
    new winston.transports.File({ filename: 'logs/application.log' }),
  ],
});

35.2 健康检查端点

@Controller('health')
exportclassHealthController {
constructor(
    private readonly prisma: PrismaService,
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
  ) {}

@Get()
asynccheckHealth() {
    const dbHealthy = awaitthis.checkDatabase();
    const cacheHealthy = awaitthis.checkCache();
    
    const isHealthy = dbHealthy && cacheHealthy;
    
    return {
      status: isHealthy ? 'healthy' : 'unhealthy',
      timestamp: newDate().toISOString(),
      dependencies: {
        database: dbHealthy ? 'up' : 'down',
        cache: cacheHealthy ? 'up' : 'down',
      },
    };
  }

privateasynccheckDatabase() {
    try {
      awaitthis.prisma.$queryRaw`SELECT 1`;
      returntrue;
    } catch {
      returnfalse;
    }
  }

privateasynccheckCache() {
    try {
      awaitthis.cacheManager.set('health-check', 'ok', 10);
      returntrue;
    } catch {
      returnfalse;
    }
  }
}

结语
NestJS 的核心价值不是让你写更少的代码,而是让团队写出一致的、可维护的、可测试的代码。对于一位资深 Node.js 架构师来说,NestJS 提供了从项目初期就能落地的架构约束,帮你在业务增长期避免技术债务的快速堆积。

当你习惯了 DI 和 AOP 带来的代码组织方式,回头看裸 Express 项目时,你会觉得那简直是“回了原始社会”。

Happy coding! 🚀

</zain>

</parameter>
</invoke>
</tool_calls>

更多推荐