架构师视角深度解析-NestJS 从零到精通 —— 面向资深 Node.js 后端架构师
适用读者:拥有 Express / Koa 等 Node.js 框架经验的资深后端工程师。
目标:1 天内通读本文即可开始生产项目开发,后续按需查阅具体章节。
目录
-
为什么是 NestJS
-
核心理念:IoC / DI / AOP
-
项目初始化与目录结构
-
Module(模块)—— 组织代码的基石
-
Controller(控制器)—— 路由与请求处理
-
Provider(提供者)—— 业务逻辑的载体
-
DTO / 数据校验(ValidationPipe)
-
异常处理(ExceptionFilter)
-
Middleware(中间件)
-
Guard(守卫)—— 认证与鉴权
-
Interceptor(拦截器)—— 响应转换与切面日志
-
Pipe(管道)—— 输入变换与校验
-
自定义装饰器
-
请求生命周期总览
-
数据库集成(TypeORM / Prisma)
-
配置管理
-
环境变量与多环境
-
日志系统
-
文件上传与静态资源
-
定时任务(Schedule)
-
微服务支持
-
GraphQL 集成
-
WebSocket 集成
-
测试策略
-
Swagger 文档生成
-
性能优化与生产部署
-
安全最佳实践
-
生产级项目目录结构
-
常见问题与避坑指南
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 |
完整请求对象 |
@Response()
/ |
res |
完整响应对象(使用后需手动 |
@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 是完美的选择,如果你面临:
-
团队规模 ≥ 3 人,且会持续增长
-
自由的 Express 容易导致代码风格混乱
-
NestJS 的架构约束能统一团队规范
-
新人上手成本大幅降低
-
-
预期业务会持续迭代、功能会快速增多
-
模块系统天然支持业务领域划分
-
松耦合设计让改动影响范围可控
-
优秀的测试能力保障迭代质量
-
-
需要构建企业级 B 端系统或中台
-
天然支持多环境配置
-
完整的安全体系(认证、鉴权)
-
符合企业级系统稳定性要求
-
-
项目会有长时间维护周期(≥ 1 年)
-
代码组织方式可扩展性强
-
模块化设计便于后续演进
-
不依赖个人英雄主义
-
NestJS 可能不是最优选择,如果你面临:
-
纯单页面静态托管或简单 CRUD
-
Express / Fastify 足够轻量
-
引入 Nest 反而增加复杂度
-
-
极致性能优先且团队技术栈偏好原生 Node
-
Fastify 原生会更轻量(相差约 15%)
-
但 NestJS + Fastify 已经非常快
-
-
项目周期极短,属于一次性工具
-
快速上线优先,架构约束可以后置
-
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 的场景分析
迁移路径选择:
- 渐进式迁移(推荐大项目)
-
保持现有 Express 服务运行
-
新功能使用 NestJS 开发
-
逐步将旧代码迁移
-
- 重写式迁移(适合新项目或小项目)
-
利用 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 生产
核心技术亮点:
- 多租户架构
// 租户中间件
@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 } });
}
}
- 高并发库存扣减
// 基于 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>
更多推荐

所有评论(0)