Skip to content

NestJS 系统学习文档 ​

从零开始系统学习 NestJS,从基础概念到企业级项目实战

版本:NestJS 12.x(当前最新稳定版,要求 Node.js ≥ 20.19 或 ≥ 22.12)


目录 ​

第一篇:NestJS 基础 ​

  1. NestJS 是什么
  2. 为什么选择 NestJS
  3. 项目初始化
  4. 项目结构

第二篇:核心概念 ​

  1. 模块(Module)
  2. 控制器(Controller)
  3. 服务与提供者(Service/Provider)
  4. 依赖注入(DI)
  5. DTO 与管道(Pipe)

第三篇:数据库集成 ​

  1. TypeORM 集成
  2. Prisma 集成
  3. 创建 RESTful API
  4. 数据库迁移

第四篇:认证与授权 ​

  1. JWT 认证
  2. 基于角色的访问控制(RBAC)
  3. Guards 守卫

第五篇:高级特性 ​

  1. 中间件(Middleware)
  2. 拦截器(Interceptor)
  3. 异常过滤器(Exception Filter)
  4. 守卫(Guard)
  5. 自定义装饰器

第六篇:测试 ​

  1. 单元测试
  2. 集成测试
  3. E2E 测试

第七篇:项目实战 ​

  1. 项目结构设计
  2. 配置管理
  3. 日志系统
  4. 文件上传
  5. Swagger 文档

第八篇:微服务与进阶 ​

  1. WebSocket(Socket.IO)
  2. 微服务基础
  3. 任务调度(Schedule)
  4. Docker 部署


第一篇:NestJS 基础 ​


1.1 NestJS 是什么 ​

NestJS 是一个用于构建高效、可扩展的 Node.js 服务端应用的框架。它使用渐进式 JavaScript(支持 TypeScript),结合了面向对象编程(OOP)、函数式编程(FP)和函数响应式编程(FRP)的元素。

核心特点:

markdown
- 底层默认使用 Express(可选 Fastify)
- 架构受 Angular 启发(模块化、依赖注入、装饰器)
- 原生 TypeScript 支持
- 内置 DTO 验证(class-validator + class-transformer)
- 强大的 CLI 工具(Nest CLI)
- 丰富的生态(TypeORM/Prisma/Mongoose/GraphQL/WebSocket)
- 测试优先(内置 Jest)

NestJS vs Express:

typescript
// Express——自由度高但无约束
const app = express();
app.get("/users", async (req, res) => {
  const users = await db.findMany();
  res.json(users);
});

// NestJS——有架构约束,更适合中大型项目
@Controller("users")
export class UserController {
  constructor(private userService: UserService) {}

  @Get()
  @UseGuards(AuthGuard)
  async findAll() {
    return this.userService.findAll();
  }
}

1.2 为什么选择 NestJS ​

和 Express 的对比 ​

维度ExpressNestJS
架构无约束(路由函数)模块化 + MVC + 依赖注入
TypeScript可选支持原生支持
代码组织自由度高有规范约束
测试需手动配置内置 Jest + 测试模块
学习曲线低中(需理解 DI、装饰器等概念)
适合项目小到中型中到大型
性能基准等同 Express(默认)

NestJS 适用场景 ​

markdown
✅ 适合:
- 中大型企业级 API 项目
- 需要完善的项目架构
- 多人协作的团队项目
- 需要 Swagger 文档的项目
- RESTful + GraphQL 混合项目
- 微服务架构
- 需要严格类型检查的项目

❌ 不太适合:
- 极简 API(Express 直接写几个路由就够了)
- 对性能极致敏感的场景(NestJS 的抽象层有轻微开销)
- 团队人数很少的小项目

1.3 项目初始化 ​

使用 Nest CLI(推荐) ​

bash
# 安装 Nest CLI
npm install -g @nestjs/cli

# 创建新项目
nest new my-nest-app
# 选择包管理器:npm / yarn / pnpm

# 启动
cd my-nest-app
npm run start:dev            # 开发模式(热重载)

# 访问 http://localhost:3000 → "Hello World!"

手动创建 ​

bash
# 也可以手动创建项目
mkdir my-nest-app && cd my-nest-app
npm init -y
npm install @nestjs/core @nestjs/common @nestjs/platform-express reflect-metadata rxjs
npm install -D typescript @types/node ts-node

Nest CLI 常用命令 ​

bash
# 生成模块
nest g module users

# 生成控制器(带 CRUD)
nest g controller users

# 生成服务
nest g service users

# 生成完整资源(CRUD 全栈)
nest g resource users
# ? What transport layer do you use? REST API
# ? Would you like to generate CRUD entry points? Yes
# 自动生成:users.module.ts + users.controller.ts + users.service.ts + DTOs

# 生成过滤器、守卫、拦截器、管道、中间件
nest g filter common/filters/http-exception
nest g guard common/guards/auth
nest g interceptor common/interceptors/logging
nest g pipe common/pipes/validation
nest g middleware common/middleware/logger

# 生成装饰器
nest g decorator common/decorators/user

1.4 项目结构 ​

标准 NestJS 项目结构 ​

src/
├── main.ts                            # 入口文件
├── app.module.ts                      # 根模块
├── app.controller.ts                  # 根控制器
├── app.service.ts                     # 根服务
├── common/                            # 公共模块
│   ├── decorators/                    # 自定义装饰器
│   │   └── user.decorator.ts
│   ├── filters/                       # 异常过滤器
│   │   └── http-exception.filter.ts
│   ├── guards/                        # 守卫
│   │   └── auth.guard.ts
│   ├── interceptors/                  # 拦截器
│   │   └── logging.interceptor.ts
│   ├── pipes/                         # 管道
│   │   └── validation.pipe.ts
│   └── middlewares/
│       └── logger.middleware.ts
├── modules/                           # 业务模块
│   ├── users/
│   │   ├── users.module.ts
│   │   ├── users.controller.ts
│   │   ├── users.service.ts
│   │   ├── dto/
│   │   │   ├── create-user.dto.ts
│   │   │   └── update-user.dto.ts
│   │   ├── entities/
│   │   │   └── user.entity.ts
│   │   └── users.controller.spec.ts
│   └── auth/
│       ├── auth.module.ts
│       ├── auth.controller.ts
│       ├── auth.service.ts
│       └── strategies/
│           └── jwt.strategy.ts
├── config/                            # 配置文件
│   └── app.config.ts
└── database/
    └── migrations/

main.ts 入口文件 ​

typescript
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { ValidationPipe } from "@nestjs/common";
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // 全局管道(DTO 验证)
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,        // 自动剔除不在 DTO 中的字段
      forbidNonWhitelisted: true, // 有不在 DTO 中的字段时报错
      transform: true,        // 自动类型转换(string "1" → number 1)
    })
  );

  // CORS
  app.enableCors({
    origin: ["http://localhost:5173"],
    credentials: true,
  });

  // Swagger 文档
  const config = new DocumentBuilder()
    .setTitle("My API")
    .setDescription("API description")
    .setVersion("1.0")
    .addBearerAuth()
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup("api/docs", app, document);

  await app.listen(3000);
  console.log(`Server running on http://localhost:3000`);
  console.log(`Swagger docs: http://localhost:3000/api/docs`);
}

bootstrap();

第二篇:核心概念 ​


2.1 模块(Module) ​

模块是 NestJS 组织代码的基本单位。每个 NestJS 应用至少有一个根模块(AppModule)。

typescript
// app.module.ts - 根模块
import { Module } from "@nestjs/common";
import { UsersModule } from "./modules/users/users.module";
import { AuthModule } from "./modules/auth/auth.module";
import { ConfigModule } from "@nestjs/config";
import { TypeOrmModule } from "@nestjs/typeorm";

@Module({
  imports: [
    // 全局配置模块
    ConfigModule.forRoot({ isGlobal: true }),
    // 数据库模块
    TypeOrmModule.forRoot({
      type: "postgres",
      host: process.env.DB_HOST,
      port: parseInt(process.env.DB_PORT),
      username: process.env.DB_USER,
      password: process.env.DB_PASSWORD,
      database: process.env.DB_NAME,
      autoLoadEntities: true,
      synchronize: process.env.NODE_ENV !== "production",
    }),
    // 业务模块
    UsersModule,
    AuthModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

模块的角色:

typescript
@Module({
  imports: [],      // 导入其他模块
  controllers: [],  // 控制器(处理 HTTP 请求)
  providers: [],    // 服务(业务逻辑,可注入)
  exports: [],      // 导出本模块的 provider 供其他模块使用
})

Feature Module(业务模块) ​

typescript
// modules/users/users.module.ts
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";
import { User } from "./entities/user.entity";

@Module({
  imports: [TypeOrmModule.forFeature([User])],  // 注册实体
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],  // 其他模块可以使用 UsersService
})
export class UsersModule {}

模块的作用域 ​

typescript
// 全局模块(用 @Global 装饰器)
@Global()
@Module({
  providers: [CommonService],
  exports: [CommonService],
})
export class CommonModule {}
// 全局模块中的 provider 在整个应用中可用,无需 imports

// 动态模块(带配置参数)
@Module({})
export class DatabaseModule {
  static forRoot(options: DatabaseOptions): DynamicModule {
    return {
      module: DatabaseModule,
      providers: [
        { provide: "DATABASE_OPTIONS", useValue: options },
        DatabaseService,
      ],
      exports: [DatabaseService],
    };
  }
}
// 使用: imports: [DatabaseModule.forRoot({ host: "localhost" })]

2.2 控制器(Controller) ​

控制器负责处理传入的 HTTP 请求并返回响应。

typescript
// modules/users/users.controller.ts
import {
  Controller, Get, Post, Put, Delete, Param, Body, Query,
  HttpCode, HttpStatus, UseGuards, ParseIntPipe,
} from "@nestjs/common";
import { UsersService } from "./users.service";
import { CreateUserDto } from "./dto/create-user.dto";
import { UpdateUserDto } from "./dto/update-user.dto";
import { JwtAuthGuard } from "../../common/guards/auth.guard";
import { ApiBearerAuth, ApiTags, ApiOperation } from "@nestjs/swagger";

@ApiTags("用户管理")           // Swagger 标签
@Controller("users")          // 路由前缀:/users
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  // POST /users
  @Post()
  @ApiOperation({ summary: "创建用户" })
  async create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }

  // GET /users
  @Get()
  @ApiOperation({ summary: "获取所有用户" })
  async findAll(
    @Query("page", ParseIntPipe) page: number = 1,
    @Query("limit", ParseIntPipe) limit: number = 10,
  ) {
    return this.usersService.findAll(page, limit);
  }

  // GET /users/:id
  @Get(":id")
  @ApiOperation({ summary: "根据 ID 获取用户" })
  async findOne(@Param("id", ParseIntPipe) id: number) {
    return this.usersService.findOne(id);
  }

  // PUT /users/:id
  @Put(":id")
  @ApiOperation({ summary: "更新用户" })
  async update(
    @Param("id", ParseIntPipe) id: number,
    @Body() updateUserDto: UpdateUserDto,
  ) {
    return this.usersService.update(id, updateUserDto);
  }

  // DELETE /users/:id
  @Delete(":id")
  @HttpCode(HttpStatus.NO_CONTENT)     // 返回 204
  @ApiOperation({ summary: "删除用户" })
  async remove(@Param("id", ParseIntPipe) id: number) {
    await this.usersService.remove(id);
  }
}

路由参数装饰器 ​

typescript
@Get(":userId/posts/:postId")
async findPost(
  @Param("userId") userId: string,
  @Param("postId") postId: string,
) {}

// 获取全部参数
@Get(":id")
async findOne(@Param() params: any) {
  // params.id
}

// Query 参数
@Get()
async findAll(@Query("page") page: number, @Query("limit") limit: number) {}

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

// 请求头
@Get()
async getHeaders(@Headers("authorization") auth: string) {}

// 自定义参数
@Get()
async find(@Ip() ip: string) {}                    // 客户端 IP
async find(@HostParam() host: string) {}           // 主机名

2.3 服务与提供者(Service/Provider) ​

服务(Service) ​

服务是 NestJS 中处理业务逻辑的核心组件。

typescript
// users.service.ts
import { Injectable, NotFoundException } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { User } from "./entities/user.entity";
import { CreateUserDto } from "./dto/create-user.dto";
import { UpdateUserDto } from "./dto/update-user.dto";

@Injectable()       // 标记为可注入的 provider
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  async create(createUserDto: CreateUserDto): Promise<User> {
    const user = this.userRepository.create(createUserDto);
    return this.userRepository.save(user);
  }

  async findAll(page = 1, limit = 10) {
    const [items, total] = await this.userRepository.findAndCount({
      skip: (page - 1) * limit,
      take: limit,
    });
    return { items, total, page, limit };
  }

  async findOne(id: number): Promise<User> {
    const user = await this.userRepository.findOneBy({ id });
    if (!user) throw new NotFoundException(`User #${id} not found`);
    return user;
  }

  async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
    const user = await this.findOne(id);
    Object.assign(user, updateUserDto);
    return this.userRepository.save(user);
  }

  async remove(id: number): Promise<void> {
    const user = await this.findOne(id);
    await this.userRepository.remove(user);
  }
}

提供者(Provider) ​

"提供者"是 NestJS 中可以被注入到其他类中的类。@Injectable() 装饰器标记的类都是提供者,服务、仓库、工厂函数等都属于提供者。

typescript
// 提供者的多种注册方式
@Module({
  providers: [
    UsersService,                              // 简写:类作为令牌
    { provide: UsersService, useClass: UsersService }, // 完整写法
    { provide: "DATABASE_CONNECTION", useValue: connection }, // 值提供者(字符串令牌)
    { provide: "MAIL_SERVICE", useFactory: (config) => new MailService(config), inject: [ConfigService] }, // 工厂提供者
    { provide: Logger, useExisting: ConsoleLogger }, // 别名
  ],
})

2.4 依赖注入(DI) ​

依赖注入是 NestJS 的核心设计模式。

typescript
// 不使用 DI 的问题
class UsersController {
  private readonly usersService = new UsersService();  // 紧耦合
  // 问题:1. 耦合度高  2. 难以测试  3. 无法替换实现
}

// 使用 DI
@Controller("users")
export class UsersController {
  constructor(
    private readonly usersService: UsersService,  // NestJS 自动注入
  ) {}
  // 测试时可以注入 Mock 实现
}

DI 的工作流程:

typescript
// 1. 注册提供者(在模块中)
@Module({
  providers: [UsersService],   // 告诉 Nest:UsersService 是一个提供者
  controllers: [UsersController],
})

// 2. 请求注入(在构造器中)
// Nest 的 DI 容器扫描构造器的参数类型
// 自动创建 UsersService 实例并注入

// 3. 作用域(Scope)
@Injectable({ scope: Scope.DEFAULT })  // 单例(默认,整个应用共享)
@Injectable({ scope: Scope.REQUEST })  // 每个请求创建新实例
@Injectable({ scope: Scope.TRANSIENT }) // 每次注入创建新实例

DI 的内部机制( Nest 是怎么"知道"要注入什么的) ​

NestJS 的依赖注入不是魔法,靠的是 TypeScript 装饰器元数据 + reflect-metadata:

  1. tsconfig.json 中开启 emitDecoratorMetadata 后,编译器会为每个被 @Injectable() 等装饰器标注的类,在构造器参数上自动生成 design:paramtypes 元数据(即参数类型列表)。
  2. @nestjs/core 的 injector 在装配模块时,通过 Reflect.getMetadata('design:paramtypes', target) 读取构造器参数类型,把它当作注入令牌(token)去容器里查找对应的 provider 实例。
  3. 找到后递归解析该 provider 自身的依赖(所以依赖图是自底向上实例化的),最终用 new 完成构造。
typescript
// 用 class 作 token 时,依赖"编译期元数据"
constructor(private usersService: UsersService) {}
// 等价的完整写法:显式给 token,不依赖元数据
constructor(@Inject(UsersService) private usersService: UsersService) {}

注意事项:

场景结果
interface / 泛型类型作注入类型类型在编译后被擦除,运行时无元数据 → 必须用字符串/Symbol token + @Inject()
未开启 emitDecoratorMetadata(如用 esbuild/swc/tsx 构建)元数据丢失,注入值为 undefined,运行时报 Cannot read properties of undefined → 需显式 @Inject()
provider 未在其模块 providers 中注册、也没有被导出导入启动时直接抛 Nest can't resolve dependencies of ...,而不是运行到调用才报错

💡 Scope 的性能含义:默认 DEFAULT(单例)零额外开销;REQUEST/TRANSIENT 会让每个请求/每次注入都走一次实例化链路,并且一个 REQUEST 级 provider 会把它依赖链上的相关实例全部降级为请求级——高并发接口里慎用它做"伪隔离",用 AsyncLocalStorage 或显式传参更可预测。


2.5 DTO 与管道(Pipe) ​

DTO(数据传输对象) ​

DTO 定义 API 接收和返回的数据结构。结合 class-validator 和 class-transformer 实现验证和转换。

typescript
// create-user.dto.ts
import {
  IsString, IsEmail, IsOptional, MinLength, MaxLength,
  IsInt, Min, IsEnum, Matches,
} from "class-validator";
import { ApiProperty } from "@nestjs/swagger";

export enum UserRole {
  ADMIN = "admin",
  USER = "user",
}

export class CreateUserDto {
  @ApiProperty({ example: "张三" })
  @IsString()
  @MinLength(2)
  @MaxLength(20)
  name: string;

  @ApiProperty({ example: "user@example.com" })
  @IsEmail()
  email: string;

  @ApiProperty({ example: "Abc12345" })
  @IsString()
  @MinLength(8)
  @Matches(/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, {
    message: "密码必须包含大小写字母和数字",
  })
  password: string;

  @ApiProperty({ enum: UserRole, default: UserRole.USER })
  @IsEnum(UserRole)
  @IsOptional()
  role?: UserRole;

  @ApiProperty({ example: 25 })
  @IsInt()
  @Min(0)
  @IsOptional()
  age?: number;
}

// update-user.dto.ts
// 使用 PartialType 继承 create dto 的所有字段(全部可选)
import { PartialType } from "@nestjs/swagger";
export class UpdateUserDto extends PartialType(CreateUserDto) {}

管道(Pipe) ​

管道主要用于数据转换和验证。NestJS 内置了多个管道。

typescript
// 使用内置管道
@Get(":id")
async findOne(
  @Param("id", ParseIntPipe) id: number,  // 字符串 "1" → 数字 1
  @Query("page", new DefaultValuePipe(1), ParseIntPipe) page: number,
) {}

// 自定义管道
import { PipeTransform, Injectable, BadRequestException } from "@nestjs/common";

@Injectable()
export class ParseObjectIdPipe implements PipeTransform {
  transform(value: any) {
    if (!value.match(/^[0-9a-fA-F]{24}$/)) {
      throw new BadRequestException("Invalid ObjectId");
    }
    return value;
  }
}

// 使用自定义管道
@Get(":id")
async findOne(@Param("id", ParseObjectIdPipe) id: string) {}

第三篇:数据库集成 ​


3.1 TypeORM 集成 ​

安装 ​

bash
npm install @nestjs/typeorm typeorm pg   # PostgreSQL
# 或 MySQL
npm install @nestjs/typeorm typeorm mysql2

配置 ​

typescript
// app.module.ts
import { TypeOrmModule } from "@nestjs/typeorm";

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: "postgres",
      host: process.env.DB_HOST || "localhost",
      port: parseInt(process.env.DB_PORT) || 5432,
      username: process.env.DB_USER || "postgres",
      password: process.env.DB_PASSWORD || "postgres",
      database: process.env.DB_NAME || "nestjs",
      entities: [__dirname + "/**/*.entity{.ts,.js}"],
      synchronize: true,           // 开发阶段自动同步表结构(生产禁用!)
      logging: process.env.NODE_ENV !== "production",
    }),
  ],
})
export class AppModule {}

实体定义 ​

typescript
// modules/users/entities/user.entity.ts
import {
  Entity, PrimaryGeneratedColumn, Column,
  CreateDateColumn, UpdateDateColumn, OneToMany, ManyToOne,
  JoinColumn,
} from "typeorm";
import { Post } from "../../posts/entities/post.entity";
import { UserRole } from "../dto/create-user.dto";

@Entity("users")                         // 指定表名
export class User {
  @PrimaryGeneratedColumn()             // 自增主键
  id: number;

  @Column({ length: 50 })               // VARCHAR(50)
  name: string;

  @Column({ unique: true })             // 唯一索引
  email: string;

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

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

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

  // 关系:一个用户有多篇文章
  @OneToMany(() => Post, (post) => post.author)
  posts: Post[];

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;
}

关系定义 ​

typescript
// 一对一:User ↔ Profile
@OneToOne(() => Profile)
@JoinColumn()                   // 拥有外键的一方需要 @JoinColumn
profile: Profile;

// 一对多:User ↔ Post
@OneToMany(() => Post, (post) => post.author)
posts: Post[];

// 多对一:Post ↔ User
@ManyToOne(() => User, (user) => user.posts)
@JoinColumn({ name: "authorId" })
author: User;

// 多对多:Post ↔ Category
@ManyToMany(() => Category)
@JoinTable()                    // 需要 @JoinTable 来创建关联表
categories: Category[];

内部机制与注意事项 ​

  • 元数据来源:@Entity()/@Column() 等装饰器把类、属性、关系写入 TypeORM 的全局元数据存储(getMetadataArgsStorage(),本质是 Reflect 元数据数组);DataSource.initialize() 时统一解析成 EntityMetadata,之后查询构建器基于它生成 SQL。因此实体类必须被 DataSource 的 entities 扫到,否则运行时报 EntityMetadataNotFound。
  • Repository 不是每实体一个连接:同一个 DataSource 内部持有一个连接池,getRepository() 返回的是共享仓库实例;真正的并发能力取决于池大小(extra: { connectionLimit },默认 10)。
  • save() vs insert() / update():save() 会先实体检查(可能多发一次 SELECT)来决定 INSERT 还是 UPDATE;高吞吐写入路径应改用 insert()/update() 明确语义。
  • ⚠️ synchronize: true 直接按实体改表结构,不产生迁移记录,可能丢数据;生产环境必须 false 并走 migration:generate + migration:run。
  • ⚠️ 关系加载默认 lazy: false 时用 JOIN 或 relations 显式加载;eager: true 会把该关系带进每一次查询,容易引发隐藏 N+1 与数据放大。
  • 💡 NestJS 的 TypeOrmModule.forFeature([User]) 只是把 getRepository(User) 包装成可注入 provider(token 为 getRepositoryToken(User)),单元测试中用 getRepositoryToken 替换成 mock 即可。

3.2 Prisma 集成 ​

Prisma 是比 TypeORM 更现代的 ORM,提供类型安全的数据库访问。

安装 ​

bash
npm install @prisma/client @nestjs/config
npm install -D prisma

# 初始化 Prisma
npx prisma init

Schema 定义 ​

prisma
// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String
  password  String
  role      Role     @default(USER)
  posts     Post[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

enum Role {
  ADMIN
  USER
}

Prisma 模块 ​

typescript
// modules/prisma/prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from "@nestjs/common";
import { PrismaClient } from "@prisma/client";

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}

// modules/prisma/prisma.module.ts
import { Global, Module } from "@nestjs/common";
import { PrismaService } from "./prisma.service";

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

在服务中使用 Prisma ​

typescript
// users.service.ts(使用 Prisma)
import { Injectable } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service";

@Injectable()
export class UsersService {
  constructor(private prisma: PrismaService) {}

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

  async findOne(id: number) {
    return this.prisma.user.findUnique({
      where: { id },
      include: { posts: true },
    });
  }

  async create(data: { name: string; email: string; password: string }) {
    return this.prisma.user.create({ data });
  }

  async update(id: number, data: { name?: string; email?: string }) {
    return this.prisma.user.update({
      where: { id },
      data,
    });
  }
}

Prisma 7 的变化(重要,版本适用说明) ​

⚠️ 本节上文(安装、prisma-client-js、import { PrismaClient } from "@prisma/client"、PrismaService extends PrismaClient 直接 $connect())是 Prisma 6.x 的经典写法,仅适用于 Prisma ≤ 6 的项目。Prisma ORM 7(2025-11 发布)包含破坏性变更,6.x 代码无法原样运行。以下变化依据 Prisma 官方升级指南(prisma.io/docs/guides/upgrade-prisma-orm/v7)与 NestJS 官方 Prisma recipe(docs.nestjs.com/recipes/prisma)整理。

1. 查询引擎去 Rust 化:必须使用 Driver Adapter ​

v7 移除了捆绑的 Rust 查询引擎,改为纯 TypeScript + WASM 的 query compiler。带来的直接后果:

  • 所有数据库都必须显式传入驱动适配器(driver adapter),连接由底层 Node.js 驱动(如 pg)负责。
  • 官方基准显示新客户端查询最高约 3.4× 更快、包体积约 90% 更小(约 14MB → 1.6MB),且天然支持 edge/serverless 运行时。
bash
# v7 安装(对照上文的 6.x 安装)
npm install @prisma/client@7 @prisma/adapter-pg pg
npm install -D prisma@7

2. 生成器从 prisma-client-js 改为 prisma-client,output 必填 ​

客户端不再默认生成进 node_modules,而是生成到项目源码目录(像普通应用代码一样对待),所有导入路径随之改变:

prisma
// v6 写法(旧,6.x 对照)
generator client {
  provider = "prisma-client-js"
}

// v7 写法(新)
generator client {
  provider     = "prisma-client"
  output       = "../src/generated/prisma"   // output 现在是必填项
  moduleFormat = "cjs"                        // NestJS 需要,见下条
}
typescript
// v6 导入
import { PrismaClient } from "@prisma/client";
// v7 导入:指向生成路径
import { PrismaClient } from "./generated/prisma/client";

💡 NestJS 特有坑:Prisma 7 默认以 ES Module 发布,与 NestJS 默认的 CommonJS 构建不兼容,官方 recipe 要求在生成器中设置 moduleFormat = "cjs"(或把整个项目迁移到 ESM + type: "module")。同时要把生成目录加入 .gitignore(生成物不再在 node_modules 里),并保留 postinstall 中的 prisma generate。

3. 配置中心迁移到 prisma.config.ts ​

prisma init 现在默认在项目根目录(package.json 旁)生成 prisma.config.ts,CLI 所需的数据库连接、schema 位置、迁移目录、seed 脚本都在这里配置;schema.prisma 中 datasource 的 url / directUrl / shadowDatabaseUrl 已弃用:

typescript
// prisma.config.ts(v7 新增)
import "dotenv/config";
import { defineConfig, env } from "prisma/config";

export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
    seed: "tsx prisma/seed.ts",
  },
  datasource: {
    url: env("DATABASE_URL"), // CLI 迁移用连接串(原 directUrl 的值也移到这里)
  },
});

⚠️ v7.0.0 起环境变量不再自动加载,必须在 prisma.config.ts 顶部显式 import "dotenv/config",否则 CLI 读不到 DATABASE_URL。NestJS 运行时侧仍靠 ConfigModule 读取 .env。

4. PrismaService 的 v7 写法 ​

typescript
// modules/prisma/prisma.service.ts(Prisma 7 + NestJS)
import { Injectable, OnModuleInit, OnModuleDestroy } from "@nestjs/common";
import { PrismaClient } from "../../generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";

@Injectable()
export class PrismaService extends PrismaClient
  implements OnModuleInit, OnModuleDestroy {
  constructor() {
    // 连接串现在交给 adapter,而不是 schema 的 datasource
    super({ adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }) });
  }

  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}

业务 Service 的 prisma.user.findMany() 等查询 API 本身不变,变的是客户端的构造、生成与配置方式。

5. CLI 与行为的其他破坏性变更速查 ​

变更点v6 行为v7 行为
prisma migrate dev / db push自动执行 prisma generate不再自动生成,必须显式运行 prisma generate;--skip-generate / --skip-seed 标志移除
自动 seedmigrate dev / migrate reset 后自动跑 seed自动 seeding 移除,必须显式 prisma db seed
$use 中间件可用已移除 → 改用 Client Extensions($extends)
Metrics preview6.14 起弃用移除,可用 adapter 或 Client Extensions 自行统计
prisma db execute支持 --schema / --url移除,连接必须在 prisma.config.ts 中配置;migrate diff 的 --from-url/--to-url 同理改为 --from-config-datasource / --to-config-datasource
连接池默认值Prisma 内置池,默认 5s 连接超时沿用底层驱动默认——pg 默认无超时(0),升级后出现挂起/超时问题需回到 adapter 层调池参数
SSL 证书校验Rust 引擎曾忽略无效证书走 node-pg,无效证书会报 P1010;需正确配置证书,或显式 ssl: { rejectUnauthorized: false }
MongoDB支持v7 暂不支持,Mongo 项目请留在 v6
运行环境—Node ≥ 20.19.0(推荐 22.x)、TypeScript ≥ 5.4

6. 怎么选版本 ​

markdown
✅ 用 Prisma 7:新项目;需要 edge/serverless 部署;在意冷启动与包体积
⚠️ 留在 Prisma 6:MongoDB 项目(v7 尚不支持);重度依赖 $use 中间件且暂无法迁移;
   团队无法接受 config 文件与生成路径的全面迁移
迁移顺序建议:先改 generator + output 并重新 generate → 建 prisma.config.ts →
   接入 driver adapter → 全局替换 @prisma/client 导入路径 → 用 satisfies/$extends
   替换 Prisma.validator/$use → 补显式 generate 与 db seed 脚本

3.3 创建 RESTful API ​

完整 CRUD 示例 ​

typescript
// users.controller.ts
@Controller("users")
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  async create(@Body() dto: CreateUserDto) {
    return this.usersService.create(dto);
  }

  @Get()
  async findAll(
    @Query("page", new DefaultValuePipe(1), ParseIntPipe) page: number,
    @Query("limit", new DefaultValuePipe(10), ParseIntPipe) limit: number,
  ) {
    return this.usersService.findAll(page, limit);
  }

  @Get(":id")
  async findOne(@Param("id", ParseIntPipe) id: number) {
    return this.usersService.findOne(id);
  }

  @Put(":id")
  async update(
    @Param("id", ParseIntPipe) id: number,
    @Body() dto: UpdateUserDto,
  ) {
    return this.usersService.update(id, dto);
  }

  @Delete(":id")
  @HttpCode(HttpStatus.NO_CONTENT)
  async remove(@Param("id", ParseIntPipe) id: number) {
    await this.usersService.remove(id);
  }
}

3.4 数据库迁移 ​

bash
# TypeORM 迁移
npx typeorm migration:create src/database/migrations/CreateUserTable
npx typeorm migration:run -d src/config/typeorm.config.ts
npx typeorm migration:revert -d src/config/typeorm.config.ts

# Prisma 迁移
npx prisma migrate dev --name add_user_table    # 开发
npx prisma migrate deploy                       # 生产
npx prisma studio                               # 数据库 UI

💡 Prisma 7 差异提醒(详见 3.2 的「Prisma 7 的变化」):v7 中 migrate dev / db push 不再自动执行 prisma generate,也不会在迁移后自动跑 seed;需要显式 npx prisma generate 与 npx prisma db seed。CI 脚本和本地工作流都要按此调整。 ​

第四篇:认证与授权 ​


4.1 JWT 认证 ​

安装 ​

bash
npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt
npm install -D @types/passport-jwt @types/bcrypt

Auth 模块 ​

typescript
// auth/auth.module.ts
import { Module } from "@nestjs/common";
import { JwtModule } from "@nestjs/jwt";
import { PassportModule } from "@nestjs/passport";
import { AuthService } from "./auth.service";
import { AuthController } from "./auth.controller";
import { UsersModule } from "../users/users.module";
import { JwtStrategy } from "./strategies/jwt.strategy";

@Module({
  imports: [
    UsersModule,
    PassportModule,
    JwtModule.register({
      secret: process.env.JWT_SECRET || "secret-key",
      signOptions: { expiresIn: "7d" },
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService, JwtStrategy],
  exports: [AuthService],
})
export class AuthModule {}

Auth 服务 ​

typescript
// auth.service.ts
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import * as bcrypt from "bcrypt";
import { UsersService } from "../users/users.service";

@Injectable()
export class AuthService {
  constructor(
    private usersService: UsersService,
    private jwtService: JwtService,
  ) {}

  async register(dto: { email: string; password: string; name: string }) {
    const hashedPassword = await bcrypt.hash(dto.password, 10);
    const user = await this.usersService.create({
      ...dto,
      password: hashedPassword,
    });
    const { password, ...result } = user;
    return result;
  }

  async login(dto: { email: string; password: string }) {
    const user = await this.usersService.findByEmail(dto.email);
    if (!user) throw new UnauthorizedException("邮箱或密码错误");

    const isPasswordValid = await bcrypt.compare(dto.password, user.password);
    if (!isPasswordValid) throw new UnauthorizedException("邮箱或密码错误");

    const payload = { sub: user.id, email: user.email, role: user.role };
    return {
      access_token: this.jwtService.sign(payload),
      user: { id: user.id, name: user.name, email: user.email },
    };
  }
}

JWT Strategy ​

typescript
// auth/strategies/jwt.strategy.ts
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { ExtractJwt, Strategy } from "passport-jwt";
import { UsersService } from "../../users/users.service";

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private usersService: UsersService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: process.env.JWT_SECRET || "secret-key",
    });
  }

  // 验证成功后,返回值会被注入到 request.user
  async validate(payload: { sub: number; email: string; role: string }) {
    const user = await this.usersService.findOne(payload.sub);
    if (!user) throw new UnauthorizedException();
    return { id: payload.sub, email: payload.email, role: payload.role };
  }
}

4.2 基于角色的访问控制(RBAC) ​

角色装饰器 ​

typescript
// common/decorators/roles.decorator.ts
import { SetMetadata } from "@nestjs/common";

export const ROLES_KEY = "roles";
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);

角色守卫 ​

typescript
// common/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { ROLES_KEY } from "../decorators/roles.decorator";

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (!requiredRoles) return true; // 没有角色要求则放行

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

使用 ​

typescript
@Controller("admin")
export class AdminController {
  @Post("users")
  @UseGuards(JwtAuthGuard, RolesGuard)  // 先验证 JWT,再检查角色
  @Roles("admin")                        // 只有 admin 可以调用
  async createUser(@Body() dto: CreateUserDto) {
    return this.usersService.create(dto);
  }
}

4.3 Guards 守卫 ​

Guard 决定请求是否由路由处理器处理。在中间件之后、拦截器/管道之前执行。

typescript
// common/guards/jwt-auth.guard.ts
import { Injectable, ExecutionContext, UnauthorizedException } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { Reflector } from "@nestjs/core";
import { IS_PUBLIC_KEY } from "../decorators/public.decorator";

@Injectable()
export class JwtAuthGuard extends AuthGuard("jwt") {
  constructor(private reflector: Reflector) {
    super();
  }

  canActivate(context: ExecutionContext) {
    // 如果路由标记为 @Public(),则不进行 JWT 验证
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (isPublic) return true;

    return super.canActivate(context);
  }

  handleRequest(err, user) {
    if (err || !user) {
      throw err || new UnauthorizedException();
    }
    return user;
  }
}

// 全局注册守卫
// app.module.ts 或 main.ts
// { provide: APP_GUARD, useClass: JwtAuthGuard }

请求生命周期全景(Request Lifecycle) ​

上面各组件分散在不同章节,官方文档(docs.nestjs.com/faq/request-lifecycle)给出的完整执行顺序如下,排查"为什么我的 Guard 没跑/拦截器顺序不对"时以这张表为准:

text
1. 进入请求 (Incoming request)
2. 中间件 Middleware
   2.1 全局绑定的中间件(app.use)
   2.2 模块绑定的中间件(按模块 imports 顺序,根模块最先)
3. 守卫 Guards
   3.1 全局 → 3.2 控制器级 → 3.3 路由级(先绑定先执行)
4. 拦截器 Interceptors(pre-controller)
   4.1 全局 → 4.2 控制器级 → 4.3 路由级
5. 管道 Pipes
   5.1 全局 → 5.2 控制器级 → 5.3 路由级 → 5.4 参数级管道
6. Controller(方法处理器)
7. Service(如果存在)
8. 拦截器 Interceptors(post-request)
   8.1 路由级 → 8.2 控制器级 → 8.3 全局级(**后进先出**,因为 RxJS Observable 包裹)
9. 异常过滤器 Exception Filters
   9.1 路由级 → 9.2 控制器级 → 9.3 全局级(从最低层级开始解析)
10. 服务端响应

三个最容易搞错的点:

组件解析顺序原因
Interceptors入站 FIFO、出站 FILO(洋葱式)拦截器返回 Observable,next.handle() 层层嵌套
Filters不是全局优先,而是路由级最先就近捕获;且异常不会在 filter 间传递——低层 filter 捕获后,高层 filter 不会再执行
Pipes路由参数级管道从最后一个参数向第一个执行官方文档明确说明的反直觉行为

⚠️ 异常过滤器只在未被捕获的异常时触发:try/catch 吞掉的异常不会走到 filter;中间件里抛出的异常由底层 Express/Fastify 异常层处理,此时只有全局 filter 生效(因为中间件执行时尚未选中路由处理器)。


第五篇:高级特性 ​


5.1 中间件(Middleware) ​

中间件在路由处理器之前执行。可以用于日志记录、请求修改等。

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

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const start = Date.now();
    res.on("finish", () => {
      const duration = Date.now() - start;
      console.log(`${req.method} ${req.originalUrl} ${res.statusCode} ${duration}ms`);
    });
    next();
  }
}

// 在模块中应用中间件
@Module({})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes("*");                  // 对所有路由
      // .forRoutes({ path: "users", method: RequestMethod.GET })  // 对特定路由
      // .exclude({ path: "auth", method: RequestMethod.POST });  // 排除路由
  }
}

5.2 拦截器(Interceptor) ​

拦截器可以在函数执行前后添加逻辑,用于日志、缓存、序列化、转换等。

typescript
// common/interceptors/logging.interceptor.ts
import {
  Injectable, NestInterceptor, ExecutionContext, CallHandler,
} from "@nestjs/common";
import { Observable, tap } from "rxjs";

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const now = Date.now();

    return next.handle().pipe(
      tap(() => {
        console.log(`${request.method} ${request.url} ${Date.now() - now}ms`);
      }),
    );
  }
}

// common/interceptors/transform.interceptor.ts
// 统一封装响应格式
@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map((data) => ({
        code: 200,
        message: "success",
        data,
        timestamp: new Date().toISOString(),
      })),
    );
  }
}

5.3 异常过滤器(Exception Filter) ​

typescript
// common/filters/http-exception.filter.ts
import {
  ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus,
} from "@nestjs/common";
import { Response } from "express";

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const message =
      exception instanceof HttpException
        ? exception.getResponse()
        : "服务器内部错误";

    response.status(status).json({
      code: status,
      message: typeof message === "string" ? message : (message as any).message,
      timestamp: new Date().toISOString(),
      path: ctx.getRequest().url,
    });
  }
}

5.4 自定义装饰器 ​

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

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

// 使用
@Get("profile")
getProfile(@CurrentUser() user: UserJwtPayload) {
  return this.usersService.findOne(user.id);
}

@Get("profile/email")
getEmail(@CurrentUser("email") email: string) {
  return email;
}

第六篇:测试 ​


6.1 单元测试 ​

NestJS 使用 Jest 作为默认测试框架。

typescript
// users.service.spec.ts
import { Test, TestingModule } from "@nestjs/testing";
import { getRepositoryToken } from "@nestjs/typeorm";
import { UsersService } from "./users.service";
import { User } from "./entities/user.entity";

describe("UsersService", () => {
  let service: UsersService;
  const mockUserRepository = {
    create: jest.fn(),
    save: jest.fn(),
    find: jest.fn(),
    findOneBy: jest.fn(),
    findAndCount: jest.fn(),
    remove: jest.fn(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UsersService,
        {
          provide: getRepositoryToken(User),
          useValue: mockUserRepository,
        },
      ],
    }).compile();

    service = module.get<UsersService>(UsersService);
  });

  it("should be defined", () => {
    expect(service).toBeDefined();
  });

  describe("findAll", () => {
    it("should return paginated users", async () => {
      const users = [{ id: 1, name: "Alice" }];
      mockUserRepository.findAndCount.mockResolvedValue([users, 1]);

      const result = await service.findAll(1, 10);

      expect(result.items).toEqual(users);
      expect(result.total).toBe(1);
      expect(mockUserRepository.findAndCount).toHaveBeenCalledWith({
        skip: 0,
        take: 10,
      });
    });
  });
});

6.2 E2E 测试 ​

typescript
// test/app.e2e-spec.ts
import { Test, TestingModule } from "@nestjs/testing";
import { INestApplication } from "@nestjs/common";
import * as request from "supertest";
import { AppModule } from "../src/app.module";

describe("Users (e2e)", () => {
  let app: INestApplication;

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

    app = moduleFixture.createNestApplication();
    await app.init();
  });

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

  it("POST /auth/register", () => {
    return request(app.getHttpServer())
      .post("/auth/register")
      .send({ email: "test@test.com", password: "Test12345", name: "Test" })
      .expect(201)
      .expect((res) => {
        expect(res.body.access_token).toBeDefined();
        expect(res.body.user.email).toBe("test@test.com");
      });
  });
});

第七篇:项目实战 ​


7.1 项目结构设计 ​

markdown
推荐的分层架构:

表现层(Presentation Layer)
  ├── Controllers(处理 HTTP 请求)
  ├── DTOs(请求/响应数据结构)
  └── Guards(认证/授权)

业务层(Business Layer)
  ├── Services(核心业务逻辑)
  ├── Strategies(认证策略)
  └── Validators(自定义验证)

数据层(Data Layer)
  ├── Entities / Models(数据模型)
  ├── Repositories(数据访问)
  └── Migrations(数据库迁移)

公共层(Common Layer)
  ├── Decorators(自定义装饰器)
  ├── Filters(异常过滤器)
  ├── Interceptors(拦截器)
  ├── Pipes(管道)
  └── Middlewares(中间件)

7.2 配置管理 ​

bash
npm install @nestjs/config
typescript
// app.module.ts
ConfigModule.forRoot({
  isGlobal: true,
  envFilePath: [`.env.${process.env.NODE_ENV}`, ".env"],
  validationSchema: Joi.object({
    NODE_ENV: Joi.string().valid("dev", "prod", "test").default("dev"),
    PORT: Joi.number().default(3000),
    DB_HOST: Joi.string().required(),
    JWT_SECRET: Joi.string().min(32).required(),
  }),
});

// config/app.config.ts
export default () => ({
  port: parseInt(process.env.PORT) || 3000,
  database: {
    host: process.env.DB_HOST,
    port: parseInt(process.env.DB_PORT) || 5432,
  },
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN || "7d",
  },
});

7.3 Swagger 文档 ​

bash
npm install @nestjs/swagger
typescript
// main.ts 中配置
const config = new DocumentBuilder()
  .setTitle("API 文档")
  .setDescription("项目接口文档")
  .setVersion("1.0")
  .addBearerAuth()
  .addTag("users", "用户管理")
  .build();

const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup("api/docs", app, document);

7.4 Docker 部署 ​

dockerfile
# Dockerfile
FROM node:24-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:24-alpine AS runner
WORKDIR /app
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
USER nodejs
EXPOSE 3000
CMD ["node", "dist/main.js"]
yaml
# docker-compose.yml
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DB_HOST=postgres
      - DB_PORT=5432
      - REDIS_HOST=redis
    depends_on:
      - postgres
      - redis

  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: nestapp
      POSTGRES_USER: nestapp
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U nestapp"]
      interval: 10s

  redis:
    image: redis:7-alpine

volumes:
  pgdata:

第八篇:微服务与进阶 ​


8.1 WebSocket(Socket.IO) ​

bash
npm install @nestjs/websockets @nestjs/platform-socket.io
typescript
// chat/chat.gateway.ts
import {
  WebSocketGateway, WebSocketServer, SubscribeMessage,
  OnGatewayConnection, OnGatewayDisconnect,
} from "@nestjs/websockets";
import { Server, Socket } from "socket.io";

@WebSocketGateway({
  cors: { origin: "*" },
  namespace: "/chat",
})
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
  @WebSocketServer()
  server: Server;

  handleConnection(client: Socket) {
    console.log(`Client connected: ${client.id}`);
  }

  handleDisconnect(client: Socket) {
    console.log(`Client disconnected: ${client.id}`);
  }

  @SubscribeMessage("message")
  handleMessage(client: Socket, payload: { text: string; room: string }) {
    // 发送到指定房间
    this.server.to(payload.room).emit("message", {
      user: client.data.user,
      text: payload.text,
      timestamp: new Date(),
    });
  }

  @SubscribeMessage("join")
  handleJoin(client: Socket, room: string) {
    client.join(room);
    client.emit("joined", room);
  }
}

8.2 微服务基础 ​

bash
npm install @nestjs/microservices
typescript
// 主应用(API Gateway)
import { MicroserviceOptions, Transport } from "@nestjs/microservices";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // 同时监听 HTTP 和 TCP 两种协议
  app.connectMicroservice<MicroserviceOptions>({
    transport: Transport.TCP,
    options: { port: 3001 },
  });

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

// 用户微服务
@Controller()
export class UserMicroserviceController {
  @MessagePattern({ cmd: "get_user" })
  getUser(@Payload() data: { id: number }) {
    return this.usersService.findOne(data.id);
  }
}

8.3 任务调度 ​

bash
npm install @nestjs/schedule
typescript
@Injectable()
export class TasksService {
  @Cron("0 0 3 * * *")           // 每天凌晨 3 点执行
  async cleanUpExpiredData() {
    await this.prisma.session.deleteMany({
      where: { expiresAt: { lt: new Date() } },
    });
  }

  @Cron(CronExpression.EVERY_30_MINUTES)
  async syncCache() {
    // 每 30 分钟同步缓存
  }

  @Timeout(5000)                  // 应用启动 5 秒后执行一次
  async startupTask() {
    console.log("应用启动成功,执行初始化任务");
  }

  @Interval(60000)                // 每 60 秒执行一次
  async healthCheck() {
    // 健康检查
  }
}

8.4 NestJS 学习路线 ​

markdown
第一阶段:基础
├── NestJS 项目结构
├── 控制器 + 路由
├── 服务 + 依赖注入
├── 模块组织
├── DTO + 管道验证

第二阶段:数据库
├── TypeORM / Prisma 集成
├── CRUD 实现
├── 关系映射
├── 数据库迁移

第三阶段:认证与授权
├── JWT 认证
├── RBAC 权限
├── Guards 守卫

第四阶段:高级特性
├── 中间件 / 拦截器 / 过滤器
├── 自定义装饰器
├── 文件上传
├── Swagger 文档

第五阶段:测试与部署
├── 单元测试 / E2E 测试
├── Docker 部署
├── 环境配置管理

第六阶段:进阶
├── WebSocket
├── 微服务
├── GraphQL
├── 任务调度
└── 性能优化

基于 VitePress 构建的个人学习文档知识库