外观
NestJS 系统学习文档
从零开始系统学习 NestJS,从基础概念到企业级项目实战
版本:NestJS 12.x(当前最新稳定版,要求 Node.js ≥ 20.19 或 ≥ 22.12)
目录
第一篇:NestJS 基础
第二篇:核心概念
第三篇:数据库集成
第四篇:认证与授权
第五篇:高级特性
第六篇:测试
第七篇:项目实战
第八篇:微服务与进阶
第一篇: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 的对比
| 维度 | Express | NestJS |
|---|---|---|
| 架构 | 无约束(路由函数) | 模块化 + 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-nodeNest 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/user1.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:
tsconfig.json中开启emitDecoratorMetadata后,编译器会为每个被@Injectable()等装饰器标注的类,在构造器参数上自动生成design:paramtypes元数据(即参数类型列表)。@nestjs/core的 injector 在装配模块时,通过Reflect.getMetadata('design:paramtypes', target)读取构造器参数类型,把它当作注入令牌(token)去容器里查找对应的 provider 实例。- 找到后递归解析该 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()vsinsert()/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 initSchema 定义
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@72. 生成器从 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 标志移除 |
| 自动 seed | migrate dev / migrate reset 后自动跑 seed | 自动 seeding 移除,必须显式 prisma db seed |
$use 中间件 | 可用 | 已移除 → 改用 Client Extensions($extends) |
| Metrics preview | 6.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/bcryptAuth 模块
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/configtypescript
// 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/swaggertypescript
// 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.iotypescript
// 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/microservicestypescript
// 主应用(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/scheduletypescript
@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
├── 任务调度
└── 性能优化