Article

服务端 nestjs

更新于:2026-07-09

第一章: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.jshttps://nodejs.org 下载 LTS 版本(建议 ≥ v18)验证版本:node -vnpm -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.jsonNest CLI 配置文件,控制生成器行为、编译选项等可自定义模板路径
tsconfig.jsonTypeScript 编译配置默认启用 strict 模式
package.json项目依赖与脚本命令包含 startstart:devbuildtest 等脚本

注: 以上为最小可运行项目的典型结构,实际项目会根据功能拆分出 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() }支持 useValueuseClassuseFactoryuseExisting 四种方式
可选依赖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)
importsimports: [OtherModule]导入其他模块以使用其导出的提供者@Module({ imports: [UsersModule] })导入后可使用该模块 exports 中的内容
exportsexports: [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: trueforbidNonWhitelisted: 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()实现 RBACconst 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 {}
必须在使用该实体的模块中注册
注入标准 Repositoryconstructor(@InjectRepository(User) private repo: Repository<User>)执行 CRUD 操作async findAll(): Promise<User[]> {
return this.repo.find();
}
Repository<T> 提供 findsavedelete 等方法
自定义 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替换默认 Repositoryproviders: [
{ provide: getRepositoryToken(User), useClass: UserRepository }
]
getRepositoryToken(User) 获取 TypeORM 的注入 token
使用 QueryBuilderthis.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,已内置
全局注册 ConfigModuleConfigModule.forRoot()启用环境变量加载@Module({
imports: [ConfigModule.forRoot()]
})
export class AppModule {}
默认加载 .env 文件,支持嵌套目录
注入 ConfigServiceconstructor(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 库
注册 JwtModuleJwtModule.register({ secret, signOptions })配置密钥和令牌选项JwtModule.register({
secret: 'my-secret-key',
signOptions: { expiresIn: '1h' },
})
密钥应从环境变量读取,禁止硬编码
注入 JwtServiceconstructor(private jwt: JwtService)生成或验证 token需在模块中注册 JwtModule
生成 Tokenjwt.sign(payload)创建 JWT 字符串const token = this.jwt.sign({ sub: user.id, username: user.name });payload 通常包含用户标识(如 sub
验证 Tokenjwt.verify(token)jwt.decode(token)解析并校验 tokentry {
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/awaitexpect().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();
可注入真实或模拟模块
使用 Supertestsupertest(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)
清理测试数据afterEachafterAll 中重置状态保证测试隔离性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 是默认底层实现
启用 GraphQLGraphQLModule.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。