第一章:NestJS 入门基础
1.1 什么是 NestJS
本小节介绍 NestJS 的基本定义、设计哲学与核心特性。
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| NestJS | 基于 TypeScript 构建的渐进式 Node.js 框架,融合了 OOP、FP 和 FRP 思想 | 需要具备 TypeScript 和 Node.js 基础 |
| 架构风格 | 受 Angular 启发,采用模块化、依赖注入、装饰器等机制 | 不强制使用 Angular,但理念相似 |
| 核心特性 | 内置支持 TypeScript、强类型、模块系统、开箱即用的测试工具、微服务支持 | 默认使用 Express,也可切换为 Fastify |
| 官方定位 | 用于构建高效、可扩展的服务器端应用程序 | 适用于 REST API、GraphQL、WebSocket、微服务等多种场景 |
1.2 环境搭建与项目初始化
本小节介绍如何安装必要工具并创建一个 NestJS 项目。
| 步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 安装 Node.js | 从 https://nodejs.org 下载 LTS 版本(建议 ≥ v18) | 验证版本:node -v 和 npm -v |
| 安装 Nest CLI | 执行命令:npm install -g @nestjs/cli | 全局安装一次即可 |
| 创建新项目 | 执行命令:nest new project-name,按提示选择包管理器(npm/yarn/pnpm) | 项目名建议使用小写和连字符 |
| 启动开发服务器 | 进入项目目录后执行:npm run start:dev | 使用 Webpack HMR,自动重启 |
| 验证项目运行 | 访问 http://localhost:3000,应看到 “Hello World!” 或默认欢迎页面 | 端口可在 main.ts 中修改 |
1.3 项目结构概览
本小节列出 NestJS 项目的标准目录结构及各文件作用。
| 文件/目录路径 | 说明 | 注意事项 |
|---|---|---|
src/main.ts | 应用入口文件,调用 NestFactory.create() 启动应用 | 可在此配置全局管道、守卫等 |
src/app.module.ts | 根模块,通过 @Module 装饰器组织控制器、提供者、导入子模块 | 所有功能模块需在此或子模块中注册 |
src/app.controller.ts | 默认控制器,处理 HTTP 请求 | 示例用,实际项目中应拆分为多个控制器 |
src/app.service.ts | 默认服务类,封装业务逻辑 | 通过依赖注入被控制器使用 |
test/ | 单元测试与 E2E 测试目录 | 使用 Jest 作为测试框架 |
nest-cli.json | Nest CLI 配置文件,控制生成器行为、编译选项等 | 可自定义模板路径 |
tsconfig.json | TypeScript 编译配置 | 默认启用 strict 模式 |
package.json | 项目依赖与脚本命令 | 包含 start、start:dev、build、test 等脚本 |
注: 以上为最小可运行项目的典型结构,实际项目会根据功能拆分出
users/、auth/等功能模块目录。
第二章:核心概念
2.1 控制器(Controller)
控制器负责处理传入的 HTTP 请求并返回响应。通过 @Controller() 装饰器定义。
| 方法/装饰器名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
@Controller() | @Controller(prefix?: string) | 声明一个控制器类,可指定路由前缀 | @Controller('users')export class UserController {} | 前缀为可选,如不传则无公共路径 |
@Get() | @Get(path?: string) | 定义 GET 请求处理方法 | @Get()findAll() { return ['user1', 'user2']; } | 路径支持参数(如 '/:id') |
@Post() | @Post(path?: string) | 定义 POST 请求处理方法 | @Post()create(@Body() body) { return body; } | 通常配合 @Body() 获取请求体 |
@Put() | @Put(path?: string) | 定义 PUT 请求处理方法 | @Put(':id')update(@Param('id') id, @Body() body) {} | 用于完整资源更新 |
@Delete() | @Delete(path?: string) | 定义 DELETE 请求处理方法 | @Delete(':id')remove(@Param('id') id) {} | 通常返回 204 No Content |
@Patch() | @Patch(path?: string) | 定义 PATCH 请求处理方法 | @Patch(':id')partialUpdate(@Param('id') id, @Body() body) {} | 用于部分更新 |
@Head() | @Head(path?: string) | 定义 HEAD 请求处理方法 | @Head()headCheck() {} | 返回响应头,无响应体 |
@Options() | @Options(path?: string) | 定义 OPTIONS 请求处理方法 | @Options()optionsHandler() {} | 用于 CORS 预检等场景 |
注: 所有路由装饰器均可省略路径,默认为
/。
2.2 提供者(Provider)与依赖注入(DI)
提供者是封装业务逻辑、数据库访问等服务的类,通过依赖注入机制被控制器或其他提供者使用。
| 方法/装饰器名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
@Injectable() | @Injectable() | 标记一个类为可注入的提供者 | @Injectable()export class UsersService { } | 必须在模块的 providers 数组中注册才能注入 |
| 构造函数注入 | constructor(private service: UsersService) | 在类中注入提供者实例 | constructor(private usersService: UsersService) {} | TypeScript 类型即注入 token,需启用 emitDecoratorMetadata |
@Inject() | @Inject(token) | 手动指定注入 token(用于非类 token) | constructor(@Inject('CACHE_MANAGER') private cache) {} | 适用于字符串、Symbol 等自定义 token |
| 自定义提供者 | { provide: 'CUSTOM_TOKEN', useValue: {} } | 在模块中定义高级提供者 | { provide: 'LOGGER', useFactory: () => new Logger() } | 支持 useValue、useClass、useFactory、useExisting 四种方式 |
| 可选依赖 | constructor(@Optional() service?: MyService) | 标记依赖为可选 | constructor(@Optional() private logger?: Logger) {} | 若未注册,值为 undefined,需做空值检查 |
注意: Nest 使用基于类型的 DI 系统,要求开启
tsconfig.json中的"emitDecoratorMetadata": true和"experimentalDecorators": true。
2.3 模块(Module)
模块用于组织应用结构,将控制器、提供者、子模块组合在一起。
| 方法/装饰器名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
@Module() | @Module({ controllers, providers, imports, exports }) | 定义一个模块 | @Module({ controllers: [AppController], providers: [AppService] })export class AppModule {} | 每个 Nest 应用至少有一个根模块(通常为 AppModule) |
imports | imports: [OtherModule] | 导入其他模块以使用其导出的提供者 | @Module({ imports: [UsersModule] }) | 导入后可使用该模块 exports 中的内容 |
exports | exports: [SomeService] | 导出提供者供其他模块使用 | @Module({ providers: [DbService], exports: [DbService] }) | 未导出的提供者仅在本模块内可用 |
| 全局模块 | @Global() + @Module(...) | 标记模块为全局,无需重复导入 | @Global()@Module({ providers: [Logger], exports: [Logger] })export class LoggerModule {} | 应谨慎使用,避免隐式依赖 |
| 动态模块 | static register(options): DynamicModule | 创建可配置的模块 | ConfigModule.forRoot({ isGlobal: true }) | 返回 { module: ConfigModule, providers: [...], exports: [...] } |
注意: 模块是单例的,每个模块在整个应用生命周期中只实例化一次。
2.4 路由与请求方法
本小节补充说明 NestJS 中路由的匹配规则与请求方法绑定机制。
| 概念/操作名称 | 说明 | 注意事项 |
|---|---|---|
| 路由拼接规则 | 控制器前缀 + 方法路径 = 最终路由 例如: @Controller('cats') + @Get(':id') → /cats/:id | 路径开头是否带 / 不影响结果 |
| 路径参数 | 使用 :paramName 定义动态段,通过 @Param('paramName') 获取 | 参数名必须与装饰器中一致 |
| 查询参数 | 通过 @Query() 获取 URL 查询字符串 | 自动解析为对象,如 ?name=alice&age=30 → { name: 'alice', age: '30' } |
| 请求体 | 通过 @Body() 获取 JSON 请求体 | 需确保客户端发送 Content-Type: application/json |
| 多装饰器组合 | 一个方法可同时使用多个参数装饰器 | 顺序无关,如 findAll(@Query() q, @Param() p) |
| 路由优先级 | Nest 按代码顺序匹配路由,精确路径优先于通配符 | 建议将具体路径写在通配符之前 |
示例:
@Controller('items')
export class ItemsController {
@Get(':id')
findOne(@Param('id') id: string, @Query('lang') lang: string) {
return { id, lang };
}
}
访问 /items/123?lang=zh 返回 { "id": "123", "lang": "zh" }
第三章:请求处理
3.1 路由参数(Param、Query、Body)
本小节介绍如何从 HTTP 请求中提取路径参数、查询参数和请求体。
| 装饰器名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
@Param() | @Param(property?: string) | 获取路由路径中的动态参数 | @Get(':id')findOne(@Param('id') id: string) {} | 若不指定 property,返回整个参数对象 { id: '123' } |
@Query() | @Query(property?: string) | 获取 URL 查询字符串参数 | @Get()find(@Query('page') page: number) {} | 查询值默认为字符串,需配合 ValidationPipe 转换类型 |
@Body() | @Body(property?: string) | 获取请求体 JSON 数据 | @Post()create(@Body() createUserDto: CreateUserDto) {} | 需客户端设置 Content-Type: application/json |
@Headers() | @Headers(name?: string) | 获取请求头字段 | @Get()getLang(@Headers('accept-language') lang: string) {} | 头字段名不区分大小写 |
@Ip() | @Ip() | 获取客户端 IP 地址 | @Post()logIp(@Ip() ip: string) {} | 取决于代理配置(如 Nginx 需传递 X-Forwarded-For) |
@Req() / @Request() | @Req() | 获取原生 Express/Fastify Request 对象 | @Get()handler(@Req() req) { return req.url; } | 破坏抽象,应尽量避免直接使用 |
@Res() / @Response() | @Res() | 获取原生 Response 对象(用于自定义响应) | @Get()custom(@Res() res) { res.status(200).send('OK'); } | 使用后 Nest 不再自动序列化返回值,需手动调用 res.send() |
注: 所有参数装饰器均可省略属性名以获取完整对象,如
@Param()返回{ id: '123' }。
3.2 请求验证(ValidationPipe 与 class-validator)
本小节介绍如何使用 ValidationPipe 和 class-validator 实现自动请求体验证。
| 概念/方法名称 | 说明 | 注意事项 |
|---|---|---|
| ValidationPipe | 内置管道,自动验证 DTO 并转换类型 | 需配合 class-validator 装饰器使用 |
| class-validator | 第三方库,提供验证装饰器(如 @IsString()) | 需安装:npm install class-validator class-transformer |
| 启用全局验证 | 在 main.ts 中:app.useGlobalPipes(new ValidationPipe()) | 推荐开启 whitelist: true 和 forbidNonWhitelisted: true |
| 常用验证装饰器 | @IsString(), @IsInt(), @Min(), @IsEmail(), @IsNotEmpty() 等 | 所有装饰器来自 class-validator 包 |
| 自动类型转换 | ValidationPipe 默认启用 transform: true,将字符串转为 number/boolean | 例如 "age": "25" → age: 25 |
| 白名单模式 | whitelist: true 会自动剔除未在 DTO 中声明的属性 | 防止客户端传入多余字段污染数据 |
| 严格模式 | forbidNonWhitelisted: true 会在存在未声明字段时报错 | 增强安全性,建议在生产环境启用 |
DTO 定义示例:
import { IsString, IsEmail, IsNotEmpty } from 'class-validator';
export class CreateUserDto {
@IsString()
@IsNotEmpty()
name: string;
@IsEmail()
email: string;
}
控制器使用示例:
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
自动触发验证,失败时抛出 400 错误。
3.3 全局/局部管道(Pipe)
管道用于转换或验证输入数据。可作用于方法参数、控制器或全局。
| 操作类型 | 语法/方式 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 局部管道(参数级) | @Body(new ValidationPipe()) | 仅对该参数应用管道 | create(@Body(new ValidationPipe()) dto: Dto) | 适用于单个参数特殊处理 |
| 局部管道(方法级) | @UsePipes(new ValidationPipe()) | 对整个方法的所有参数应用 | @UsePipes(new ValidationPipe())@Post() create(@Body() dto) {} | 覆盖全局管道 |
| 控制器级管道 | 在控制器类上使用 @UsePipes() | 对控制器所有方法生效 | @UsePipes(new ValidationPipe())export class UserController {} | 优先级高于全局,低于方法级 |
| 全局管道 | app.useGlobalPipes(new ValidationPipe()) | 对整个应用生效 | 在 main.ts 中调用 | 无法注入依赖(因在模块外),如需 DI 应在 AppModule 中注册 |
| 带依赖的全局管道 | 在 AppModule providers 中注册并导出 | 支持注入服务(如日志) | { provide: APP_PIPE, useClass: LoggingValidationPipe } | 需导入 APP_PIPE token(来自 @nestjs/core) |
APP_PIPE 用法示例:
import { APP_PIPE } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_PIPE,
useValue: new ValidationPipe({ whitelist: true }),
},
],
})
export class AppModule {}
注意: 推荐方式,支持 DI 且可配置。
3.4 异常过滤器(Exception Filter)
异常过滤器用于捕获未处理的异常并返回统一格式的错误响应。
| 概念/方法名称 | 说明 | 注意事项 |
|---|---|---|
@Catch() | 装饰器,指定过滤器捕获的异常类型 | 可捕获多个:@Catch(HttpException, BadRequestException) |
ExceptionFilter 接口 | 必须实现 catch(exception, host) 方法 | host 可获取请求上下文(HTTP、WebSocket 等) |
| 内置异常类 | HttpException, BadRequestException, NotFoundException 等 | 所有内置异常继承自 HttpException |
| 全局异常过滤器 | app.useGlobalFilters(new HttpExceptionFilter()) | 无法注入依赖,推荐通过 APP_FILTER 提供 |
| 上下文获取 | const ctx = host.switchToHttp(); const request = ctx.getRequest(); | 支持 HTTP、RPC、WebSocket 三种上下文 |
| 返回自定义 JSON | 通过 response.status(status).json({ error }) | 避免暴露内部错误细节(如 stack trace) |
自定义异常过滤器示例:
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const status = exception.getStatus();
response.status(status).json({
statusCode: status,
message: exception.message,
timestamp: new Date().toISOString(),
});
}
}
全局注册(支持 DI):
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
}
在 AppModule 的 providers 中注册。
注意: 未捕获的非
HttpException(如 TypeError)默认返回 500,建议在生产环境统一捕获所有异常。
第四章:中间件与拦截器
4.1 中间件(Middleware)
中间件在路由处理函数之前调用,常用于日志记录、身份验证前检查、请求预处理等。
| 概念/方法名称 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 中间件接口 | 实现 NestMiddleware 接口或使用函数式中间件 | 定义中间件逻辑 | export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { console.log('Request...'); next(); }} | 必须调用 next() 否则请求挂起 |
| 函数式中间件 | (req, res, next) => { ... } | 简单中间件可不实现接口 | const logger = (req, res, next) => { console.log('Log'); next(); }; | 无法注入依赖(无构造函数) |
| 模块中注册中间件 | 在模块的 configure() 方法中使用 consumer.apply().forRoutes() | 将中间件绑定到特定路由 | export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer.apply(LoggerMiddleware).forRoutes('cats'); }} | 仅在实现了 NestModule 的模块中有效 |
| 全局中间件 | app.use(middleware) | 对所有路由生效 | app.use(logger); | 在 main.ts 中调用,无法注入依赖 |
| 路由通配符 | 支持 *, :id, RouteInfo 对象等 | 灵活匹配路由 | .forRoutes({ path: 'ab*cd', method: RequestMethod.GET }) | 可指定 HTTP 方法(RequestMethod 枚举) |
| 多中间件链 | .apply(M1, M2).forRoutes(...) | 按顺序执行多个中间件 | .apply(AuthMiddleware, LoggerMiddleware).forRoutes(UserController) | 执行顺序为 apply 参数顺序 |
注意: 中间件是 Express/Fastify 原生概念,Nest 仅提供集成方式;不支持依赖注入(除非通过模块注册类中间件)。
4.2 拦截器(Interceptor)
拦截器在方法执行前后介入,可用于日志、性能监控、响应包装、异常处理等。
| 概念/方法名称 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
@Injectable() + NestInterceptor | 实现 intercept(context, next) 方法 | 定义拦截器 | @Injectable()export class LoggingInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler) { console.log('Before...'); return next.handle().pipe(tap(() => console.log('After'))); }} | 必须返回 next.handle()(Observable) |
ExecutionContext | 提供方法元数据(如控制器、处理器、参数) | 获取当前执行上下文 | const handler = context.getHandler();const cls = context.getClass(); | 支持 HTTP、WebSocket、RPC |
CallHandler | 包装原始方法调用 | 控制方法执行流程 | return next.handle().pipe(map(data => ({ data, timestamp: Date.now() }))); | 可使用 RxJS 操作符转换响应 |
| 局部拦截器 | @UseInterceptors(LoggingInterceptor) | 应用于方法或控制器 | @UseInterceptors(LoggingInterceptor)@Get() find() {} | 可叠加多个 |
| 全局拦截器 | app.useGlobalInterceptors(new LoggingInterceptor()) | 全局生效 | 在 main.ts 中调用 | 无法注入依赖 |
| 带依赖的全局拦截器 | 通过 APP_INTERCEPTOR 提供 | 支持 DI | { provide: APP_INTERCEPTOR, useClass: TransformInterceptor } | 推荐方式,可在 AppModule providers 中注册 |
| 响应包装示例 | 使用 map 修改返回值 | 统一 API 格式 | return next.handle().pipe(map(data => ({ success: true, data }))); | 需注意异步流处理 |
注意: 拦截器基于 RxJS,返回的是 Observable,适合处理异步逻辑;若需同步操作,可使用
tap。
4.3 守卫(Guard)
守卫决定是否允许请求继续执行,常用于权限控制、认证校验。
| 概念/方法名称 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
CanActivate 接口 | 实现 canActivate(context) 方法,返回 boolean 或 Promise/Observable | 定义访问控制逻辑 | @Injectable()export class AuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); return !!request.headers.authorization; }} | 返回 false 时抛出 403 Forbidden |
ExecutionContext | 获取请求、路由、用户信息等 | 判断权限依据 | const req = context.switchToHttp().getRequest(); | 可结合 JWT 解析用户角色 |
| 局部守卫 | @UseGuards(AuthGuard) | 应用于方法或控制器 | @UseGuards(AuthGuard)@Post() create() {} | 可组合多个守卫(全部需返回 true) |
| 全局守卫 | app.useGlobalGuards(new AuthGuard()) | 全局生效 | 在 main.ts 中调用 | 无法注入依赖 |
| 带依赖的全局守卫 | 通过 APP_GUARD 提供 | 支持注入服务(如 AuthService) | { provide: APP_GUARD, useClass: RolesGuard } | 推荐方式 |
| 角色守卫示例 | 结合自定义装饰器 @Roles() | 实现 RBAC | const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler());return requiredRoles.some(role => user.roles.includes(role)); | 需配合 Reflector 使用 |
| Reflector | 注入 Reflector 服务读取元数据 | 获取方法/类上的自定义元数据 | constructor(private reflector: Reflector) {} | 常用于读取 @SetMetadata() 设置的角色 |
自定义元数据装饰器示例:
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
守卫中使用元数据:
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) return true; // 无角色要求
return user.roles.some(r => roles.includes(r));
注意: 守卫在管道和拦截器之前执行;若守卫返回 false,后续逻辑(包括控制器方法)不会执行。
第五章:数据持久化
5.1 TypeORM 集成
本小节介绍如何在 NestJS 项目中集成 TypeORM 并配置数据库连接。
| 操作/概念名称 | 说明 | 注意事项 |
|---|---|---|
| 安装依赖 | 执行命令:npm install @nestjs/typeorm typeorm mysql2(以 MySQL 为例) | 根据数据库类型选择驱动(如 pg for PostgreSQL, sqlite3 for SQLite) |
| 注册 TypeOrmModule | 在 AppModule 中使用 TypeOrmModule.forRoot() | 支持同步模式(仅开发)和异步配置(推荐) |
同步配置示例:
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'test',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true, // 自动同步 schema(禁用于生产)
}),
],
})
export class AppModule {}
注意:
synchronize: true会自动创建/更新表结构,但可能丢失数据,禁止在生产环境使用。
异步配置(推荐):
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
type: 'mysql',
host: configService.get('DB_HOST'),
port: configService.get('DB_PORT'),
username: configService.get('DB_USERNAME'),
password: configService.get('DB_PASSWORD'),
database: configService.get('DB_NAME'),
entities: [User],
synchronize: false,
logging: true,
}),
inject: [ConfigService],
})
注意: 推荐用于生产环境,支持环境变量管理。实体注册方式可通过
entities: [User]或路径通配符。
5.2 Repository 模式与自定义 Repository
本小节介绍如何通过 Repository 访问数据库,并扩展自定义方法。
| 概念/方法名称 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
@Entity() | 标记类为数据库实体 | 定义数据模型 | @Entity()export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string;} | 需从 typeorm 导入装饰器 |
| 注册实体到模块 | 在 Feature Module 中使用 TypeOrmModule.forFeature([User]) | 使 Repository 可注入 | @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], controllers: [UsersController],})export class UsersModule {} | 必须在使用该实体的模块中注册 |
| 注入标准 Repository | constructor(@InjectRepository(User) private repo: Repository<User>) | 执行 CRUD 操作 | async findAll(): Promise<User[]> { return this.repo.find();} | Repository<T> 提供 find、save、delete 等方法 |
| 自定义 Repository 类 | 继承 Repository<T> | 封装复杂查询逻辑 | @Injectable()export class UserRepository extends Repository<User> { async findByName(name: string): Promise<User[]> { return this.find({ where: { name } }); }} | 新版 TypeORM 推荐使用继承方式或单独服务类 |
| 注册自定义 Repository | 在模块 providers 中提供,并设置 useClass | 替换默认 Repository | providers: [ { provide: getRepositoryToken(User), useClass: UserRepository }] | getRepositoryToken(User) 获取 TypeORM 的注入 token |
| 使用 QueryBuilder | this.repo.createQueryBuilder('user') | 构建复杂 SQL 查询 | const users = await this.repo .createQueryBuilder('user') .where('user.age > :age', { age: 18 }) .getMany(); | 支持链式调用,类型安全 |
注意: 自定义 Repository 不再推荐使用
@EntityRepository()(TypeORM v0.3+ 已移除),应改用服务类或继承 Repository。
5.3 数据迁移(Migration)
本小节介绍如何使用 TypeORM 的迁移功能管理数据库 schema 变更。
| 操作步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 初始化迁移目录 | 执行命令:npx typeorm migration:create -n CreateUsersTable | 需在项目根目录有 ormconfig.json 或通过 CLI 配置 |
| 配置 CLI 连接 | 创建 ormconfig.ts 或在 package.json 中配置脚本 | 示例:"typeorm": "typeorm-ts-node-commonjs" |
| 运行迁移 | npm run typeorm migration:run | 应用所有未执行的迁移 |
| 回滚迁移 | npm run typeorm migration:revert | 仅回滚最近一次迁移 |
| 自动生成迁移(开发) | npm run typeorm migration:generate -- -n Init | 基于实体与当前数据库差异生成 SQL |
| 生产迁移策略 | 手动编写迁移文件,经测试后部署 | 禁止使用 synchronize: true 和自动生成迁移 |
| NestJS 中运行迁移 | 可在 main.ts 中调用 app.get(Connection).runMigrations() | 适用于容器化部署时自动迁移 |
编写迁移文件:
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "user" (...)`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "user"`);
}
package.json 脚本示例:
{
"scripts": {
"typeorm": "typeorm-ts-node-commonjs",
"migration:run": "npm run typeorm -- migration:run",
"migration:revert": "npm run typeorm -- migration:revert",
"migration:generate": "npm run typeorm -- migration:generate -d src/data-source.ts"
}
}
注意: TypeORM v0.3+ 推荐使用 DataSource 替代 Connection,迁移命令需指定数据源文件(如
-d src/data-source.ts)。
第六章:配置与环境管理
6.1 配置模块(ConfigModule)
ConfigModule 是 NestJS 官方提供的配置管理模块,基于 dotenv 实现环境变量加载与类型安全访问。
| 操作/方法名称 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 安装依赖 | npm install @nestjs/config | 引入配置模块 | — | 无需额外安装 dotenv,已内置 |
| 全局注册 ConfigModule | ConfigModule.forRoot() | 启用环境变量加载 | @Module({ imports: [ConfigModule.forRoot()]})export class AppModule {} | 默认加载 .env 文件,支持嵌套目录 |
| 注入 ConfigService | constructor(private config: ConfigService) | 读取配置值 | const port = this.config.get('PORT');const dbHost = this.config.get<string>('DATABASE_HOST'); | 支持泛型指定返回类型 |
| 设置默认值 | config.get('KEY', 'default') | 提供 fallback 值 | this.config.get('TIMEOUT', 5000) | 若环境变量未定义,返回默认值 |
| 验证配置(schema) | 通过 validate 选项传入 Joi 或自定义函数 | 确保必要配置存在且合法 | ConfigModule.forRoot({ validate: (config) => { if (!config.PORT) throw new Error('PORT is required'); return config; }}) | 推荐在应用启动时校验关键配置 |
| 禁用 dotenv 加载 | isGlobal: true, load: [] | 仅使用自定义配置加载器 | 当需要从 Consul、Vault 等外部源加载时使用 | 默认会自动加载 .env |
注意:
ConfigModule.forRoot()应只在根模块(AppModule)中调用一次,通常设置isGlobal: true以便全局注入 ConfigService。
6.2 环境变量管理
本小节介绍如何组织和管理多环境(开发、测试、生产)的配置文件。
| 概念/操作名称 | 说明 | 注意事项 |
|---|---|---|
.env 文件 | 根目录下的默认环境变量文件 | 格式:KEY=value,不支持注释和复杂类型 |
| 多环境文件 | 支持 .env.development, .env.production 等 | 通过 NODE_ENV 自动加载对应文件 |
| 优先级规则 | 系统环境变量 > .env.local > .[NODE_ENV] > .env | .env.local 通常用于本地覆盖(应加入 .gitignore) |
| 类型安全配置对象 | 创建 ConfigFactory 返回结构化配置 | 避免到处使用字符串 key |
| 自定义配置文件 | 使用 load: [databaseConfig] 加载 TypeScript 配置函数 | 支持复杂逻辑和类型推导 |
自定义配置工厂示例(database.config.ts):
export default () => ({
database: {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10) || 3306,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
name: process.env.DB_NAME,
},
});
在模块中加载自定义配置:
ConfigModule.forRoot({
load: [databaseConfig],
isGlobal: true,
})
注入结构化配置:
const dbConfig = this.config.get('database');
// 或
const host = this.config.get('database.host');
注意: 敏感信息(如密码)不应硬编码在代码中,应通过 CI/CD 或密钥管理服务注入。
6.3 动态模块注册
动态模块允许在运行时根据配置参数生成模块定义,常用于数据库连接、第三方 SDK 集成等场景。
| 概念/方法名称 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
静态 forRoot() | 返回 DynamicModule 对象 | 提供同步配置接口 | static forRoot(options: Options): DynamicModule { return { module: MyModule, providers: [ { provide: 'OPTIONS', useValue: options }, MyService, ], exports: [MyService], };} | 适用于简单配置 |
异步 forRootAsync() | 支持工厂函数和依赖注入 | 从 ConfigService 获取配置 | static forRootAsync({ imports: [ConfigModule], useFactory: (config: ConfigService) => ({ apiKey: config.get('API_KEY'), }), inject: [ConfigService],}) | 推荐用于生产环境 |
DynamicModule 结构 | 必须包含 module 字段,可选 providers, imports, exports 等 | 描述模块元数据 | 与 @Module() 装饰器参数一致 | 返回对象即模块定义 |
| 注册自定义提供者 | 在动态模块中定义 { provide: TOKEN, useValue: ... } | 将配置注入到服务中 | providers: [ { provide: 'MAILER_OPTIONS', useValue: options }, MailerService,] | 服务通过 @Inject('MAILER_OPTIONS') 获取 |
| 全局动态模块 | 在 forRoot() 中设置 global: true | 避免重复导入 | return { module: LoggerModule, global: true, providers: [...], exports: [...],}; | 谨慎使用,防止隐式依赖 |
使用动态模块示例(同步方式):
@Module({
imports: [
MailerModule.forRoot({ apiKey: 'xxx' }),
],
})
export class AppModule {}
使用动态模块示例(异步方式,推荐):
@Module({
imports: [
MailerModule.forRootAsync({
useFactory: (config) => ({ apiKey: config.get('MAIL_KEY') }),
inject: [ConfigService],
}),
],
})
注意: 动态模块是 NestJS 实现插件化和可配置性的核心机制,官方模块(如 TypeOrmModule、ConfigModule)均采用此模式。
第七章:认证与授权
7.1 JWT 认证实现
本小节介绍如何在 NestJS 中手动生成和验证 JSON Web Token(JWT),实现无状态认证。
| 操作/方法名称 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 安装依赖 | npm install @nestjs/jwt | 引入官方 JWT 模块 | — | 内部使用 jsonwebtoken 库 |
| 注册 JwtModule | JwtModule.register({ secret, signOptions }) | 配置密钥和令牌选项 | JwtModule.register({ secret: 'my-secret-key', signOptions: { expiresIn: '1h' },}) | 密钥应从环境变量读取,禁止硬编码 |
| 注入 JwtService | constructor(private jwt: JwtService) | 生成或验证 token | — | 需在模块中注册 JwtModule |
| 生成 Token | jwt.sign(payload) | 创建 JWT 字符串 | const token = this.jwt.sign({ sub: user.id, username: user.name }); | payload 通常包含用户标识(如 sub) |
| 验证 Token | jwt.verify(token) 或 jwt.decode(token) | 解析并校验 token | try { const payload = this.jwt.verify(token);} catch (e) { throw new UnauthorizedException();} | verify() 会校验签名和过期时间;decode() 仅解析不校验 |
| 登录接口示例 | 返回 { access_token: token } | 供客户端存储并用于后续请求 | @Post('login')async login(@Body() credentials: LoginDto) { const user = await this.authService.validate(credentials); if (!user) throw new UnauthorizedException(); return { access_token: this.jwt.sign({ sub: user.id }) };} | 响应格式遵循 OAuth2 Bearer Token 规范 |
| 客户端使用 | 在请求头携带 Authorization: Bearer <token> | 传递认证凭证 | — | 所有受保护路由需校验该头 |
注意: JWT 一旦签发无法主动失效(除非使用黑名单),适合短期令牌;长期会话建议结合 Refresh Token 机制。
7.2 Passport 集成
NestJS 官方推荐使用 @nestjs/passport 封装 Passport.js 策略,实现标准化认证流程。
| 概念/方法名称 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 安装依赖 | npm install @nestjs/passport passport passport-jwt | 引入 Passport 及 JWT 策略 | — | 根据认证方式选择策略(如 passport-local) |
| 创建策略类 | 继承 PassportStrategy(Strategy) | 实现认证逻辑 | @Injectable()export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: 'my-secret-key', }); } async validate(payload: any) { return { userId: payload.sub, username: payload.username }; }} | validate() 返回值将附加到 req.user |
| 注册策略 | 在模块 providers 中注册 JwtStrategy | 使策略生效 | @Module({ imports: [PassportModule, JwtModule], providers: [JwtStrategy],})export class AuthModule {} | 必须导入 PassportModule |
| 使用守卫 | @UseGuards(AuthGuard('jwt')) | 保护路由 | @UseGuards(AuthGuard('jwt'))@Get('profile')getProfile(@Request() req) { return req.user;} | 'jwt' 对应策略名称(默认为类名小写) |
| 自定义守卫封装 | 创建 JwtAuthGuard extends AuthGuard('jwt') | 统一错误处理 | @Injectable()export class JwtAuthGuard extends AuthGuard('jwt') { canActivate(context: ExecutionContext) { return super.canActivate(context); }} | 可重写 handleRequest() 自定义异常 |
| 多策略支持 | @UseGuards(AuthGuard(['jwt', 'local'])) | 同时支持多种认证方式 | — | 按顺序尝试,任一成功即通过 |
注意:
ExtractJwt.fromAuthHeaderAsBearerToken()要求客户端发送Authorization: Bearer <token>;密钥应通过 ConfigService 注入。
7.3 基于角色的访问控制(RBAC)
在认证基础上,通过角色(Role)限制用户对资源的访问权限。
| 概念/操作名称 | 说明 | 注意事项 |
|---|---|---|
| 用户角色模型 | 在 User 实体中添加 roles: string[] 字段 | 例如 ['user', 'admin'] |
| 自定义元数据装饰器 | @SetMetadata('roles', ['admin']) | 标记路由所需角色 |
| Roles 装饰器定义 | export const Roles = (...roles: string[]) => SetMetadata('roles', roles); | 便于使用 |
| RolesGuard 实现 | 读取路由元数据并与用户角色比对 | 需注入 Reflector |
| Reflector 使用 | this.reflector.get<string[]>('roles', context.getHandler()) | 获取方法上的角色要求 |
RolesGuard 完整示例:
@Injectable()
export class RolesGuard implements CanActivate {
constructor(
private reflector: Reflector,
private authService: AuthService,
) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) return true; // 无角色限制
const request = context.switchToHttp().getRequest();
const user = request.user;
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
路由使用示例:
@Roles('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
@Delete(':id')
remove(@Param('id') id: string) {
return this.usersService.delete(id);
}
模块注册 RolesGuard:
providers: [
{
provide: APP_GUARD,
useClass: RolesGuard,
},
]
注意: 守卫执行顺序为 JwtAuthGuard → RolesGuard;若未登录,
req.user为 undefined,需在 RolesGuard 中做空值检查。
第八章:测试
8.1 单元测试(Jest)
单元测试用于验证服务、管道、守卫等提供者(Provider)的独立逻辑是否正确。
| 概念/方法名称 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
@nestjs/testing | 提供 Test.createTestingModule() | 创建轻量级测试模块 | const moduleRef = await Test.createTestingModule({ providers: [UsersService, MockUserRepository],}).compile(); | 仅加载被测单元及其依赖 |
| 获取服务实例 | moduleRef.get(UsersService) | 获取待测服务 | const service = moduleRef.get(UsersService); | 自动解析依赖注入 |
| 模拟依赖(Mock) | 使用类或对象替代真实提供者 | 隔离外部依赖(如数据库) | const mockRepo = { find: jest.fn().mockResolvedValue([{ id: 1, name: 'Alice' }]),}; | 推荐使用 jest.fn() 模拟方法行为 |
| 测试异步方法 | 使用 async/await 或 expect().resolves | 验证 Promise 返回值 | expect(await service.findAll()).toEqual([{ id: 1, name: 'Alice' }]); | 避免遗漏 await 导致测试假通过 |
| 验证方法调用 | expect(mockFn).toHaveBeenCalledWith(...) | 确保依赖方法被正确调用 | await service.createUser({ name: 'Bob' });expect(mockRepo.save).toHaveBeenCalledWith({ name: 'Bob' }); | 常用于验证副作用(如保存、发送邮件) |
| 测试异常场景 | 使用 expect().rejects.toThrow() | 验证错误抛出 | await expect(service.findById('invalid')).rejects.toThrow(NotFoundException); | 适用于验证业务规则校验 |
| 覆盖率报告 | 运行 npm run test:cov | 生成测试覆盖率报告 | — | 默认输出到 coverage/ 目录 |
注意: 单元测试应聚焦单一职责,避免启动 HTTP 服务器或连接真实数据库;所有外部依赖必须模拟。
8.2 端到端测试(E2E)
E2E 测试启动完整的 NestJS 应用,通过 HTTP 请求验证整个请求-响应流程。
| 概念/方法名称 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 创建测试应用 | Test.createTestingModule().createNestApplication() | 启动完整 Nest 应用 | const moduleRef = await Test.createTestingModule({ imports: [AppModule],}).compile();const app = moduleRef.createNestApplication();await app.init(); | 可注入真实或模拟模块 |
| 使用 Supertest | supertest(app.getHttpServer()) | 发送 HTTP 请求并断言响应 | const server = app.getHttpServer();await request(server) .get('/users') .expect(200) .expect([{ id: 1, name: 'Alice' }]); | request 来自 supertest 包 |
| 模拟数据库 | 在 E2E 模块中替换 TypeORM 提供者 | 避免污染生产数据 | TypeOrmModule.forRoot({ type: 'sqlite', database: ':memory:', entities: [User], synchronize: true,}) | 推荐使用内存数据库(如 SQLite in-memory) |
| 清理测试数据 | 在 afterEach 或 afterAll 中重置状态 | 保证测试隔离性 | afterEach(async () => { await clearDatabase(); // 自定义清理函数}); | 防止测试间相互影响 |
| 测试认证流程 | 先登录获取 token,再携带 token 请求受保护接口 | 验证完整认证链路 | const loginRes = await request(server).post('/auth/login').send(credentials);const token = loginRes.body.access_token;await request(server).get('/profile').set('Authorization', \Bearer ${token}`).expect(200);` | 模拟真实用户行为 |
| 关闭应用 | await app.close() | 释放资源(如数据库连接) | afterAll(async () => { await app.close();}); | 避免测试进程挂起 |
| 运行 E2E 测试 | npm run test:e2e | 执行 e2e 目录下的测试 | — | 默认使用独立的 jest-e2e.json 配置 |
E2E 测试文件结构示例:
// src/users/users.e2e-spec.ts
describe('Users (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication();
await app.init();
});
it('/users (GET)', () => {
return request(app.getHttpServer())
.get('/users')
.expect(200);
});
afterAll(async () => {
await app.close();
});
});
注意: E2E 测试速度较慢,应聚焦关键业务路径;数据库应使用独立测试实例或内存模式;避免在 CI 中连接生产数据库。
第九章:部署与性能优化
9.1 构建与打包
本小节介绍如何将 NestJS 项目编译为生产就绪的 JavaScript 代码并优化打包结构。
| 操作/概念名称 | 说明 | 注意事项 |
|---|---|---|
| 默认构建命令 | npm run build(调用 nest build) | 输出到 dist/ 目录 |
| 构建配置文件 | tsconfig.build.json | 排除测试文件和非必要资源 |
| 生产依赖安装 | npm install --production | 仅安装 dependencies(不含 devDependencies) |
| 启动生产服务 | node dist/main | 运行编译后的入口文件 |
| 使用 Webpack 打包(可选) | 配置 nest-cli.json 启用 Webpack | 生成单文件 bundle,提升启动速度 |
Webpack 配置示例:
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"webpack": true,
"plugins": ["@nestjs/cli/webpack/plugin"]
}
}
注意: TypeORM 实体路径在 Webpack 模式下需使用字符串字面量(不能用
__dirname + '...'),建议改用实体类数组注册。
9.2 Docker 部署
本小节介绍如何使用 Docker 容器化 NestJS 应用,实现环境一致性与快速部署。
| 操作步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 编写 Dockerfile | 多阶段构建:build 阶段 + runtime 阶段 | 减小镜像体积 |
| 使用 node:alpine | 基础镜像选择轻量级 Alpine Linux | 镜像更小,但缺少部分系统库 |
| 复制 package.json | 先复制依赖文件,利用 Docker 层缓存 | 加速后续构建 |
| 安装生产依赖 | RUN npm ci --only=production | 避免安装 devDependencies |
| 复制构建产物 | COPY --from=builder /app/dist ./dist | 从 build 阶段拷贝编译结果 |
| 设置工作目录 | WORKDIR /app | 统一应用根目录 |
| 暴露端口 | EXPOSE 3000 | 声明容器监听端口 |
| 启动命令 | CMD ["node", "dist/main"] | 运行生产服务 |
标准 Dockerfile 示例:
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM node:18-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
COPY .env ./
EXPOSE 3000
CMD ["node", "dist/main"]
构建与运行命令:
docker build -t my-nest-app .
docker run -p 3000:3000 my-nest-app
注意: 敏感信息(如数据库密码)不应写入
.env文件并提交到镜像,应通过-e参数或 Docker secrets 注入。
9.3 性能监控与日志
本小节介绍如何在生产环境中实现 NestJS 应用的可观测性(日志、指标、追踪)。
| 工具/方法名称 | 说明 | 注意事项 |
|---|---|---|
| 内置日志 | Logger.log(), Logger.error() | 来自 @nestjs/common |
| Winston 集成 | 安装 @nestjs/winston,替换默认 Logger | 结构化日志、多传输(文件、控制台、ELK) |
| Prometheus 指标 | 使用 @willsoto/nestjs-prometheus 暴露 /metrics | 监控请求率、延迟、错误率 |
| OpenTelemetry | 使用 @nestjs/otel 实现分布式追踪 | 跟踪跨服务调用链路 |
| 日志中间件 | 自定义中间件记录请求耗时、状态码 | const start = Date.now(); next();console.log(\[${req.method}] ${req.url} - ${Date.now() - start}ms`);` |
| 异常日志上报 | 在全局异常过滤器中集成 Sentry/Datadog | 捕获未处理错误 |
| 健康检查端点 | 使用 @nestjs/terminus 提供 /health | 供 Kubernetes 或负载均衡器探测 |
Winston 配置示例:
import * as winston from 'winston';
const logger = new winston.Logger({
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
),
}),
],
});
@Module({
imports: [
WinstonModule.forRoot({
transports: logger.transports,
}),
],
})
export class AppModule {}
Prometheus 指标示例:
@Controller('metrics')
export class MetricsController {
@Get()
getMetrics(@Res() res) {
res.set('Content-Type', promClient.register.contentType);
res.end(promClient.register.metrics());
}
}
注意: 生产日志应避免记录 PII(个人身份信息);性能监控应覆盖关键业务路径;健康检查应真实反映服务可用性。
第十章:高级特性
10.1 微服务架构支持
NestJS 原生支持构建微服务应用,通过 @nestjs/microservices 提供多种传输层(Transport)协议。
| 概念/方法名称 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 安装依赖 | npm install @nestjs/microservices | 引入微服务模块 | — | 根据传输协议额外安装驱动(如 ioredis, kafka-node) |
| 创建微服务应用 | NestFactory.createMicroservice() | 启动非 HTTP 服务 | const app = await NestFactory.createMicroservice(AppModule, { transport: Transport.TCP, options: { port: 3001 },});await app.listen(); | 替代 create(),不监听 HTTP 端口 |
| 支持的传输协议 | Transport.TCP, REDIS, KAFKA, MQTT, NATS, RABBITMQ | 选择消息中间件 | — | 每种协议需对应客户端库(如 ioredis for Redis) |
| 控制器装饰器 | @MessagePattern('user.create') | 定义消息处理路由 | @MessagePattern('user.create')createUser(data: CreateUserDto) { return this.usersService.create(data);} | 类似 HTTP 的 @Post(),但基于消息主题 |
| 事件模式 | @EventPattern('user.deleted') | 处理广播事件(无响应) | @EventPattern('user.deleted')handleUserDeleted(data: { id: string }) { this.logger.log(\User ${data.id} deleted`);<br>}` | 适用于日志、通知等 fire-and-forget 场景 |
| 客户端代理 | ClientsModule.register() | 调用其他微服务 | ClientsModule.register([{ name: 'USER_SERVICE', transport: Transport.TCP, options: { host: 'localhost', port: 3001 },}]) | 在消费者模块中注册 |
| 注入客户端 | @Inject('USER_SERVICE') client: ClientProxy | 发送消息或事件 | this.client.send('user.get', id).toPromise();this.client.emit('user.updated', payload); | send() 用于请求-响应,emit() 用于事件广播 |
| 错误处理 | 微服务抛出异常会被序列化传递 | 需在客户端捕获 | try { await firstValueFrom(this.client.send(...));} catch (err) { // 处理 RpcException} | 使用 firstValueFrom() 将 Observable 转为 Promise |
注意: 微服务间通信默认使用 JSON 序列化;生产环境建议启用消息持久化、重试机制和死信队列。
10.2 WebSocket 与实时通信
NestJS 通过 @nestjs/websockets 支持 WebSocket 和 Socket.IO 实现实时双向通信。
| 概念/方法名称 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 安装依赖 | npm install @nestjs/websockets @nestjs/platform-socket.io | 引入 WebSocket 模块 | — | 默认使用 Socket.IO,也可选原生 WebSocket |
| 创建网关 | @WebSocketGateway() | 定义 WebSocket 服务端点 | @WebSocketGateway({ cors: true, namespace: '/events',})export class EventsGateway { @SubscribeMessage('msgToServer') handleMessage(client: Socket, payload: string): string { return 'msgToClient'; }} | 网关自动绑定到 / 或指定 namespace |
| 客户端连接 | 前端使用 io('http://localhost:3000/events') | 建立 WebSocket 连接 | — | 需与网关 namespace 一致 |
| 广播消息 | this.server.emit('event', data) | 向所有客户端推送 | constructor(private server: Server) {}this.server.to(room).emit('update', data); | Server 来自 socket.io |
| 加入房间 | client.join('room1') | 实现分组通信 | @SubscribeMessage('joinRoom')joinRoom(client: Socket, room: string) { client.join(room);} | 适用于聊天室、实时协作等场景 |
| 身份认证 | 在 handleConnection 中验证 token | 限制未授权连接 | handleConnection(client: Socket) { const token = client.handshake.auth.token; if (!this.authService.validate(token)) { client.disconnect(); }} | 可结合 JWT 实现 |
| 异常过滤器 | @UseFilters(new WsExceptionFilter()) | 捕获网关内异常 | @Catch(WsException)export class WsExceptionFilter implements ExceptionFilter { catch(exception: WsException, host: ArgumentsHost) { const client = host.switchToWs().getClient(); client.emit('exception', exception.getError()); }} | 避免连接断开 |
注意: Socket.IO 提供自动重连、房间管理、ACK 机制等高级功能;若需轻量级通信,可使用原生 WebSocket(
platform-ws)。
10.3 GraphQL 集成
NestJS 官方支持 GraphQL,提供 Code First(推荐)和 Schema First 两种开发模式。
| 概念/方法名称 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 安装依赖 | npm install @nestjs/graphql @nestjs/apollo graphql apollo-server-express | 引入 GraphQL 模块 | — | Apollo Server 是默认底层实现 |
| 启用 GraphQL | GraphQLModule.forRoot() | 配置 GraphQL 服务 | GraphQLModule.forRoot({ autoSchemaFile: join(process.cwd(), 'src/schema.gql'), sort: true,}) | autoSchemaFile 自动生成 schema |
| 定义对象类型 | @ObjectType() + @Field() | 声明 GraphQL 类型 | @ObjectType()export class User { @Field() id: string; @Field() name: string;} | 字段需显式标记 @Field() |
| 定义 Resolver | @Resolver(() => User) | 实现查询和变更逻辑 | @Resolver(() => User)export class UserResolver { constructor(private usersService: UsersService) {} @Query(() => [User]) users() { return this.usersService.findAll(); }} | 方法对应 GraphQL query/mutation |
| 输入类型 | @InputType() | 定义 mutation 参数结构 | @InputType()export class CreateUserInput { @Field() name: string;} | 用于 @Args('input') input: CreateUserInput |
| 查询参数 | @Args('id') id: string | 获取 GraphQL 参数 | @Query(() => User)user(@Args('id') id: string) { return this.usersService.findOne(id);} | 对应 schema 中的 (id: ID!) |
| 关系解析 | @ResolveField() | 懒加载关联数据 | @ResolveField(() => [Post])posts(@Parent() user: User) { return this.postsService.findByUser(user.id);} | 避免 N+1 问题,建议配合 DataLoader |
| 认证守卫 | @UseGuards(GqlAuthGuard) | 保护 GraphQL 接口 | @Query(() => User)@UseGuards(GqlAuthGuard)profile(@Context() context) { return context.req.user;} | 从 context.req 获取用户信息 |
| Playground | 访问 /graphql | 内置 GraphQL IDE | — | 生产环境应禁用:introspection: false, playground: false |
注意: Code First 模式通过 TypeScript 类生成 schema,类型安全且易于维护;生产环境务必关闭 introspection 和 playground。