一、NestJS 是什么
NestJS 是一个用于构建高效、可靠和可扩展的服务端应用程序的 Node.js 框架。
为什么选 NestJS
| 对比维度 | Express | Koa | NestJS |
|---|---|---|---|
| 架构规范 | 无,自由组织 | 无,自由组织 | 强制模块化,企业级规范 |
| TypeScript | 可选 | 可选 | 原生支持,一等公民 |
| 依赖注入 | 无 | 无 | 内置完整的 DI 容器 |
| 学习曲线 | 低 | 低 | 中等 |
| 适合场景 | 小型 API | 小型 API | 中大型企业级项目 |
NestJS 的底层
NestJS 默认基于 Express(也可切换到 Fastify),但它对 Express 做了深度封装。你写的不是 Express 代码,而是 NestJS 的装饰器 + 类的代码。
二、核心概念:装饰器(Decorator)
装饰器是 NestJS 的灵魂。不理解装饰器,就不可能理解 NestJS。
什么是装饰器
装饰器本质上是一个函数,它接收目标对象作为参数,可以修改、扩展、或给目标附加元数据。
TypeScript 的装饰器在编译后依然存在于 JavaScript 中(配合 reflect-metadata),这是 class-validator 等库能做运行时校验的前提。
四种装饰器
// 1. 类装饰器 — 修饰整个类@Controller('user')export class UserController {}// 2. 方法装饰器 — 修饰类的方法@Get(':id')findOne(@Param('id') id: string) {}// 3. 属性装饰器 — 修饰类的属性@InjectRepository(User)private userRepo: Repository<User>;// 4. 参数装饰器 — 修饰方法的参数@Body() body: CreateUserDto,@Query() query: PaginationDto,@Param('id') id: string装饰器的本质
// @Controller('user') 等价于:Controller('user')(UserController);// 它做的事情大概是:function Controller(prefix: string) { return function (target: Function) { Reflect.defineMetadata('path', prefix, target); // ... 其他元数据 };}NestJS 内部大量使用 Reflect.defineMetadata 和 Reflect.getMetadata 来读写元数据,这就是为什么项目里必须引入 reflect-metadata。
三、核心概念:依赖注入(Dependency Injection)
依赖注入是 NestJS 最核心的设计模式,理解了它,你就理解了 NestJS 的运行机制。
问题场景
// ❌ 手动管理依赖 — 耦合、难测试、难维护export class UserController { private userService = new UserService(new UserRepository(new Database()));}依赖注入的解法
// ✅ 由 NestJS 容器自动注入依赖export class UserController { constructor(private readonly userService: UserService) {}}你只需要在构造函数里声明"我需要什么",NestJS 的 IoC 容器会自动帮你创建实例并注入进来。
DI 容器的工作流程
1. NestJS 启动,扫描所有 Module2. 分析每个 Module 的 providers 列表3. 为每个 provider 创建实例4. 分析构造函数的参数类型(通过 reflect-metadata)5. 自动注入对应的依赖实例6. 如果依赖的实例还没创建,先递归创建它Provider 的三种写法
// 写法 1:简写 — 类名即 tokenproviders: [UserService]// 写法 2:标准写法 — 可以指定具体实现providers: [ { provide: UserService, useClass: UserService, },]// 写法 3:值注入 — 常用于注入配置或常量providers: [ { provide: 'DATABASE_URL', useValue: 'mongodb://localhost:27017/cake', },]// 写法 4:工厂注入 — 用于动态创建依赖providers: [ { provide: 'ASYNC_CONFIG', useFactory: async (configService: ConfigService) => { return configService.get('database'); }, inject: [ConfigService], },]四、核心概念:模块化(Module)
模块是 NestJS 组织代码的基本单位。每个应用至少有一个根模块(AppModule)。
Module 装饰器的四个属性
@Module({ imports: [], // 导入其他模块(使用别的模块的 provider) controllers: [], // 本模块的控制器(处理 HTTP 请求) providers: [], // 本模块的服务(业务逻辑) exports: [], // 导出的 provider(让其他模块也能注入)})export class AdminModule {}模块之间的关系
AppModule(根模块)├── imports: ConfigModule ← 全局配置├── imports: AdminModule ← 管理员模块│ ├── controllers: AdminController│ └── providers: AdminService├── imports: StoreModule ← 店铺模块│ ├── controllers: StoreController│ ├── providers: StoreService│ └── exports: StoreService ← 导出后,其他模块可以注入 StoreService└── imports: ProductModule ← 商品模块 ├── controllers: ProductController ├── providers: ProductService └── imports: StoreModule ← 如果 ProductModule 需要用 StoreService模块间共享服务
// store.module.ts — 导出 StoreService@Module({ controllers: [StoreController], providers: [StoreService], exports: [StoreService], // 关键:导出})export class StoreModule {}// product.module.ts — 导入 StoreModule 后就能注入 StoreService@Module({ imports: [StoreModule], // 导入 controllers: [ProductController], providers: [ProductService],})export class ProductModule {}// product.service.ts — 直接注入@Injectable()export class ProductService { constructor(private readonly storeService: StoreService) {}}五、五大核心组件详解
5.1 Controller — 控制器
控制器负责处理传入的 HTTP 请求,返回响应给客户端。
职责:接收请求 → 调用 Service → 返回结果。Controller 不应该包含业务逻辑。
import { Controller, Get, Post, Put, Delete, Param, Body, Query } from '@nestjs/common';import { UserService } from './user.service';import { CreateUserDto } from './dto/create-user.dto';@Controller('user')export class UserController { constructor(private readonly userService: UserService) {} // GET /user @Get() findAll() { return this.userService.findAll(); } // GET /user/:id @Get(':id') findOne(@Param('id') id: string) { return this.userService.findOne(id); } // POST /user @Post() create(@Body() dto: CreateUserDto) { return this.userService.create(dto); } // PUT /user/:id @Put(':id') update(@Param('id') id: string, @Body() dto: UpdateUserDto) { return this.userService.update(id, dto); } // DELETE /user/:id @Delete(':id') remove(@Param('id') id: string) { return this.userService.remove(id); }}HTTP 方法装饰器速查
| 装饰器 | HTTP 方法 | 示例 |
|---|---|---|
@Get() | GET | @Get('profile') → GET /user/profile |
@Post() | POST | @Post('login') → POST /user/login |
@Put() | PUT | @Put(':id') → PUT /user/123 |
@Patch() | PATCH | @Patch(':id') → PATCH /user/123 |
@Delete() | DELETE | @Delete(':id') → DELETE /user/123 |
@All() | 所有方法 | @All('health') → 任意方法 /user/health |
参数装饰器速查
| 装饰器 | 取值来源 | 示例 |
|---|---|---|
@Body() | 请求体 | @Body() dto: CreateUserDto |
@Param('key') | 路径参数 | @Param('id') id: string |
@Query('key') | 查询参数 | @Query('page') page: number |
@Headers('key') | 请求头 | @Headers('authorization') token: string |
@Req() | 原始请求对象 | @Req() request: Request |
@Res() | 原始响应对象 | @Res() response: Response |
路由前缀与子路由
@Controller('api/v1/user') // 类级别的路由前缀export class UserController { @Get('profile') // 最终路径:GET /api/v1/user/profile getProfile() {} @Get(':id/posts') // 最终路径:GET /api/v1/user/:id/posts getUserPosts(@Param('id') id: string) {}}5.2 Service — 服务
Service 是业务逻辑的载体。所有的数据处理、数据库操作、第三方 API 调用都应该放在 Service 里。
职责:纯业务逻辑,不关心 HTTP 请求和响应。
import { Injectable, NotFoundException } from '@nestjs/common';import { CreateUserDto } from './dto/create-user.dto';@Injectable()export class UserService { // 模拟数据库 private users: any[] = []; findAll() { return this.users; } findOne(id: string) { const user = this.users.find(u => u.id === id); if (!user) { throw new NotFoundException(`用户 ${id} 不存在`); } return user; } create(dto: CreateUserDto) { const user = { id: Date.now().toString(), ...dto, }; this.users.push(user); return user; } update(id: string, dto: UpdateUserDto) { const index = this.users.findIndex(u => u.id === id); if (index === -1) { throw new NotFoundException(`用户 ${id} 不存在`); } this.users[index] = { ...this.users[index], ...dto }; return this.users[index]; } remove(id: string) { const index = this.users.findIndex(u => u.id === id); if (index === -1) { throw new NotFoundException(`用户 ${id} 不存在`); } return this.users.splice(index, 1)[0]; }}Controller 与 Service 的职责边界
Controller(门面) Service(核心)┌─────────────────────┐ ┌─────────────────────┐│ 接收 HTTP 请求 │ │ 业务逻辑处理 ││ 参数提取与校验 │ 调用 → │ 数据库 CRUD 操作 ││ 调用 Service │ │ 第三方 API 调用 ││ 返回 HTTP 响应 │ ← 返回 │ 数据转换与计算 ││ │ │ ││ 不应包含: │ │ 不应包含: ││ - 数据库操作 │ │ - HTTP 相关逻辑 ││ - 复杂业务计算 │ │ - 请求/响应对象操作 │└─────────────────────┘ └─────────────────────┘5.3 Module — 模块
见 。
5.4 Middleware — 中间件
中间件是在路由处理函数之前调用的函数。可以访问 req、res 和 next。
// logger.middleware.tsimport { Injectable, NestMiddleware } from '@nestjs/common';import { Request, Response, NextFunction } from 'express';@Injectable()export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); next(); }}// 在 Module 中注册export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('*'); // 应用到所有路由 }}5.5 Guard — 守卫
守卫用于权限控制,决定请求是否可以被处理。
// auth.guard.tsimport { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';@Injectable()export class AuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); const token = request.headers.authorization; // 验证 token 逻辑... return !!token; }}// 使用方式@UseGuards(AuthGuard)@Controller('admin')export class AdminController {}六、DTO 与参数校验
DTO(Data Transfer Object)用于定义接口的入参结构,配合 class-validator 做运行时校验。
⚠️ 核心规则:DTO 必须用 class,不能用 interface
// ❌ 错误 — interface 编译后消失,class-validator 无法校验interface CreateUserDto { name: string; age: number;}// ✅ 正确 — class 编译后依然存在,装饰器可以正常工作export class CreateUserDto { @IsString() @IsNotEmpty({ message: '用户名不能为空' }) name: string; @IsInt() @Min(0) @Max(150) age: number;}常用校验装饰器
import { IsString, IsInt, IsEmail, IsBoolean, IsOptional, IsNotEmpty, Min, Max, Length, IsArray, IsEnum, Matches, IsUrl, IsPhoneNumber,} from 'class-validator';export class CreateProductDto { @IsString() @IsNotEmpty({ message: '商品名称不能为空' }) @Length(1, 50, { message: '商品名称长度 1-50 个字符' }) name: string; @IsInt() @Min(0, { message: '价格不能为负数' }) price: number; @IsString() @IsOptional() // 可选字段 description?: string; @IsEmail({}, { message: '邮箱格式不正确' }) email: string; @IsEnum(['pending', 'active', 'disabled']) status: string; @IsArray() @IsString({ each: true }) // 数组中每个元素都校验 tags: string[];}class-transformer 的作用
// 前端传来:{ "price": "99", "isActive": "true" }// 字符串类型的数字和布尔值export class CreateProductDto { @Transform(({ value }) => parseInt(value)) price: number; @Transform(({ value }) => value === 'true') isActive: boolean;}// 开启 transform: true 后,class-transformer 会自动转换类型// ValidationPipe 配置:new ValidationPipe({ whitelist: true, // 过滤掉 DTO 中未定义的字段 forbidNonWhitelisted: true, // 传了未定义的字段直接报错 transform: true, // 启用类型转换})七、异常处理
NestJS 内置了一套完整的异常层次结构。
内置 HTTP 异常
import { BadRequestException, // 400 UnauthorizedException, // 401 ForbiddenException, // 403 NotFoundException, // 404 ConflictException, // 409 UnprocessableEntityException, // 422 InternalServerErrorException, // 500} from '@nestjs/common';// 在 Service 中抛出异常@Injectable()export class UserService { findOne(id: string) { const user = this.users.find(u => u.id === id); if (!user) { throw new NotFoundException(`用户 ${id} 不存在`); } return user; } create(dto: CreateUserDto) { const exists = this.users.find(u => u.email === dto.email); if (exists) { throw new ConflictException('邮箱已被注册'); } // ... }}自定义异常过滤器
// common/filters/http-exception.filter.tsimport { ExceptionFilter, Catch, ArgumentsHost, HttpException,} from '@nestjs/common';import { Response } from 'express';@Catch(HttpException)export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const status = exception.getStatus(); const exceptionResponse = exception.getResponse(); const message = typeof exceptionResponse === 'string' ? exceptionResponse : (exceptionResponse as any).message || 'error'; response.status(status).json({ code: status, message: Array.isArray(message) ? message.join('; ') : message, data: null, }); }}异常过滤器的挂载级别
// 1. 全局级别 — 在 main.ts 中app.useGlobalFilters(new HttpExceptionFilter());// 2. 控制器级别 — 用装饰器@UseFilters(HttpExceptionFilter)@Controller('user')export class UserController {}// 3. 方法级别 — 用装饰器@UseFilters(HttpExceptionFilter)@Post()create(@Body() dto: CreateUserDto) {}八、拦截器(Interceptor)
拦截器可以:在请求前后添加逻辑、转换响应数据、覆盖异常处理、扩展基本方法行为。
拦截器能做什么
请求进入 │ ▼┌─────────────────────────────┐│ 拦截器 - 请求前逻辑 │ ← 记录日志、权限检查、计时开始│ (intercept - before handle) │└─────────────────────────────┘ │ ▼┌─────────────────────────────┐│ Controller → Service │ ← 正常业务处理└─────────────────────────────┘ │ ▼┌─────────────────────────────┐│ 拦截器 - 响应后逻辑 │ ← 包装响应格式、记录耗时、转换数据│ (intercept - after handle) │└─────────────────────────────┘ │ ▼响应返回全局响应包装拦截器
// common/interceptors/transform.interceptor.tsimport { Injectable, NestInterceptor, ExecutionContext, CallHandler,} from '@nestjs/common';import { Observable } from 'rxjs';import { map } from 'rxjs/operators';export interface Response<T> { code: number; message: string; data: T;}@Injectable()export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> { intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> { return next.handle().pipe( map((data) => ({ code: 200, message: 'success', data, })), ); }}计时拦截器(实用示例)
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common';import { Observable, tap } from 'rxjs';@Injectable()export class TimingInterceptor implements NestInterceptor { private readonly logger = new Logger('TimingInterceptor'); intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const start = Date.now(); return next.handle().pipe( tap(() => { const elapsed = Date.now() - start; const handler = context.getHandler().name; this.logger.log(`${handler} 耗时 ${elapsed}ms`); }), ); }}拦截器的挂载级别
// 1. 全局级别app.useGlobalInterceptors(new TransformInterceptor());// 2. 控制器级别@UseInterceptors(TimingInterceptor)@Controller('user')export class UserController {}// 3. 方法级别@UseInterceptors(TimingInterceptor)@Get()findAll() {}九、管道(Pipe)
管道用于数据转换和验证。NestJS 内置了两个管道:
ValidationPipe— 配合 class-validator 做参数校验ParseIntPipe— 将字符串转为整数ParseBoolPipe— 将字符串转为布尔值ParseUUIDPipe— 校验 UUID 格式ParseArrayPipe— 将字符串转为数组
内置管道的使用
// ParseIntPipe — 自动将路径参数转为数字@Get(':id')findOne(@Param('id', ParseIntPipe) id: number) { // id 保证是 number 类型 return this.userService.findOne(id);}// ParseUUIDPipe — 自动校验 UUID 格式@Get(':id')findOne(@Param('id', ParseUUIDPipe) id: string) { // id 保证是合法的 UUID return this.userService.findOne(id);}// 组合使用@Post()create( @Body(new ValidationPipe()) dto: CreateUserDto, @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,) {}自定义管道
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';@Injectable()export class ParseObjectIdPipe implements PipeTransform<string> { transform(value: string): string { if (!/^[0-9a-fA-F]{24}$/.test(value)) { throw new BadRequestException(`${value} 不是合法的 ObjectId`); } return value; }}// 使用@Get(':id')findOne(@Param('id', ParseObjectIdPipe) id: string) {}十、环境变量与配置管理
@nestjs/config 基础用法
// .envPORT=3000DATABASE_URL=mongodb://localhost:27017/cakeJWT_SECRET=my-secret-key// app.module.tsimport { ConfigModule } from '@nestjs/config';@Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, // 全局可用,不用每个模块都导入 envFilePath: '.env', // 指定 .env 文件路径 }), ],})export class AppModule {}// 任意 Service 中使用import { ConfigService } from '@nestjs/config';@Injectable()export class DatabaseService { constructor(private configService: ConfigService) {} connect() { const url = this.configService.get<string>('DATABASE_URL'); const port = this.configService.get<number>('PORT', 3000); // 带默认值 }}配置命名空间(推荐)
// config/database.config.tsexport default () => ({ database: { host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT, 10) || 3306, username: process.env.DB_USER || 'root', password: process.env.DB_PASS || '', name: process.env.DB_NAME || 'cake', },});// config/jwt.config.tsexport default () => ({ jwt: { secret: process.env.JWT_SECRET || 'default-secret', expiresIn: process.env.JWT_EXPIRES_IN || '7d', },});// app.module.ts@Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, load: [databaseConfig, jwtConfig], // 加载命名空间配置 }), ],})export class AppModule {}// 使用const dbHost = this.configService.get('database.host');const jwtSecret = this.configService.get('jwt.secret');十一、中间件(Middleware)详解
与拦截器的区别
| 维度 | 中间件 (Middleware) | 拦截器 (Interceptor) |
|---|---|---|
| 执行时机 | 路由处理之前 | 路由处理前后都能介入 |
| 能否访问响应 | 能(原生 Express 对象) | 能(通过 RxJS 操作符) |
| 能否修改响应 | 能(手动调用 res.json) | 能(pipe + map 操作) |
| 依赖注入 | 只能在类中间件中使用 | 完全支持 |
| 典型用途 | 日志、CORS、body-parser | 响应包装、计时、缓存 |
函数式中间件
// 简单场景用函数式,不需要类export function logger(req: Request, res: Response, next: NextFunction) { console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); next();}// 在 Module 中注册consumer.apply(logger).forRoutes('*');中间件的条件路由
export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(AuthMiddleware) .forRoutes( { path: 'admin/*', method: RequestMethod.ALL }, { path: 'user/profile', method: RequestMethod.GET }, ); // 排除某些路由 consumer .apply(LoggerMiddleware) .exclude( { path: 'health', method: RequestMethod.GET }, ) .forRoutes('*'); }}十二、守卫(Guard)详解
守卫 vs 中间件 vs 拦截器
请求 → 中间件 → 守卫 → 拦截器(before) → Pipe → Controller → 拦截器(after) → 响应中间件:最早执行,适合通用预处理(日志、CORS)守卫: 在中间件之后,适合做权限判断(能否访问这个路由)拦截器:在守卫之后,适合做响应处理(包装、转换)角色守卫实战
// roles.decorator.ts — 自定义装饰器import { SetMetadata } from '@nestjs/common';export const Roles = (...roles: string[]) => SetMetadata('roles', roles);// roles.guard.tsimport { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';import { Reflector } from '@nestjs/core';@Injectable()export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler()); if (!requiredRoles) { return true; // 没有设置角色要求,放行 } const { user } = context.switchToHttp().getRequest(); return requiredRoles.some(role => user.roles?.includes(role)); }}// 使用@UseGuards(AuthGuard, RolesGuard)@Roles('admin', 'superadmin')@Delete(':id')remove(@Param('id') id: string) { return this.userService.remove(id);}全局守卫
// main.tsapp.useGlobalGuards(new AuthGuard());十三、数据库集成(TypeORM / Prisma)
TypeORM 方案
npm install @nestjs/typeorm typeorm mysql2# 或 postgres: npm install pg# 或 sqlite: npm install better-sqlite3// app.module.tsimport { TypeOrmModule } from '@nestjs/typeorm';@Module({ imports: [ TypeOrmModule.forRoot({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'cake', entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: true, // 开发环境自动同步表结构,生产环境必须关掉 }), ],})export class AppModule {}// user.entity.tsimport { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';@Entity('user')export class User { @PrimaryGeneratedColumn() id: number; @Column({ length: 50 }) name: string; @Column({ unique: true }) email: string; @Column({ default: true }) isActive: boolean;}// user.module.ts@Module({ imports: [TypeOrmModule.forFeature([User])], // 注册实体 controllers: [UserController], providers: [UserService],})export class UserModule {}// user.service.ts@Injectable()export class UserService { constructor( @InjectRepository(User) private userRepo: Repository<User>, ) {} findAll() { return this.userRepo.find(); } findOne(id: number) { return this.userRepo.findOneBy({ id }); } create(dto: CreateUserDto) { const user = this.userRepo.create(dto); return this.userRepo.save(user); } async update(id: number, dto: UpdateUserDto) { await this.userRepo.update(id, dto); return this.findOne(id); } remove(id: number) { return this.userRepo.delete(id); }}Prisma 方案(更现代)
npm install prisma @prisma/clientnpx prisma init// prisma/schema.prismadatasource db { provider = "mysql" url = env("DATABASE_URL")}generator client { provider = "prisma-client-js"}model User { id Int @id @default(autoincrement()) name String @db.VarChar(50) email String @unique isActive Boolean @default(true) createdAt DateTime @default(now())}npx prisma migrate dev --name initnpx prisma generate// prisma.service.tsimport { Injectable, OnModuleInit } from '@nestjs/common';import { PrismaClient } from '@prisma/client';@Injectable()export class PrismaService extends PrismaClient implements OnModuleInit { async onModuleInit() { await this.$connect(); }}// user.service.ts@Injectable()export class UserService { constructor(private prisma: PrismaService) {} findAll() { return this.prisma.user.findMany(); } findOne(id: number) { return this.prisma.user.findUnique({ where: { id } }); } create(dto: CreateUserDto) { return this.prisma.user.create({ data: dto }); }}十四、项目实战架构建议
推荐的目录结构
src/├── main.ts # 应用入口├── app.module.ts # 根模块├── common/ # 全局公共基础设施│ ├── filters/ # 异常过滤器│ ├── interceptors/ # 拦截器│ ├── pipes/ # 自定义管道│ ├── guards/ # 守卫│ ├── decorators/ # 自定义装饰器│ └── dto/ # 公共 DTO├── config/ # 配置文件│ ├── database.config.ts│ └── jwt.config.ts├── modules/ # 业务模块│ ├── auth/ # 认证模块│ │ ├── dto/│ │ ├── auth.controller.ts│ │ ├── auth.service.ts│ │ ├── auth.module.ts│ │ ├── auth.guard.ts│ │ └── strategies/ # Passport 策略│ ├── user/│ │ ├── dto/│ │ │ ├── create-user.dto.ts│ │ │ └── update-user.dto.ts│ │ ├── entities/ # TypeORM 实体│ │ │ └── user.entity.ts│ │ ├── user.controller.ts│ │ ├── user.service.ts│ │ └── user.module.ts│ └── ...└── database/ # 数据库相关 ├── migrations/ # 数据库迁移 └── seeds/ # 种子数据请求的完整生命周期
HTTP 请求 │ ▼中间件 (Middleware) ← 日志、CORS、body-parser │ ▼守卫 (Guard) ← 认证、角色权限 │ ▼拦截器 (Interceptor) ← 请求前处理 │ ▼管道 (Pipe) ← 参数校验、类型转换 │ ▼控制器 (Controller) ← 路由分发 │ ▼服务 (Service) ← 业务逻辑 │ ▼拦截器 (Interceptor) ← 响应包装 │ ▼异常过滤器 (Filter) ← 异常捕获(如有错误) │ ▼HTTP 响应十五、常见问题与避坑指南
1. DTO 用 class 不用 interface
// ❌ interface 编译后消失,class-validator 无法工作interface CreateUserDto { name: string; }// ✅ class 编译后保留,装饰器正常运行class CreateUserDto { @IsString() name: string;}2. 循环依赖问题
// 当 A 模块依赖 B 模块,B 模块又依赖 A 模块时// 解决方案:使用 forwardRef@Module({ imports: [forwardRef(() => ModuleB)], exports: [ServiceA],})export class ModuleA {}@Module({ imports: [forwardRef(() => ModuleA)], exports: [ServiceB],})export class ModuleB {}3. reflect-metadata 必须引入
// main.ts 最顶部import 'reflect-metadata';// NestJS CLI 生成的项目通常已自动引入,但手动搭建时别漏4. 同步 vs 异步模块注册
// 同步ConfigModule.forRoot({ isGlobal: true })// 异步 — 需要等待外部资源初始化ConfigModule.forRootAsync({ useFactory: async () => { const config = await loadConfig(); return config; }, inject: [SomeService],})5. 全局模块 vs 按需导入
// 标记为全局模块后,所有模块都可以直接注入,不需要 imports@Global()@Module({ providers: [ConfigService], exports: [ConfigService],})export class ConfigModule {}// 滥用全局模块会导致依赖关系不清晰// 建议只有 ConfigModule、DatabaseModule 这类基础设施用 @Global()6. ValidationPipe 的 whitelist 行为
// DTO 只定义了 name 和 ageclass CreateUserDto { @IsString() name: string; @IsInt() age: number;}// 前端传来:{ name: '二胖', age: 18, hacker: 'evil' }// whitelist: true → 自动过滤掉 hacker,只保留 name 和 age// forbidNonWhitelisted: true → 直接报错 400,拒绝请求// transform: true → 自动将 "18" 转为 187. Swagger 文档(可选但推荐)
npm install @nestjs/swagger swagger-ui-dist// main.tsimport { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';const config = new DocumentBuilder() .setTitle('甜品店 API') .setDescription('Cake Shop Mini Program API') .setVersion('1.0') .build();const document = SwaggerModule.createDocument(app, config);SwaggerModule.setup('api/docs', app, document);// 访问 http://localhost:3000/api/docs 查看文档附录:常用命令速查
# 创建新项目npx @nestjs/cli new project-name# 创建模块nest g module modules/usernest g controller modules/usernest g service modules/user# 一步创建模块 + 控制器 + 服务nest g resource modules/user# 构建项目nest build# 启动开发模式(热重载)nest start --watch# 运行测试npm run testnpm run test:e2e教程到此结束。核心就是三件事:装饰器、依赖注入、模块化。把这三个吃透,NestJS 就没什么神秘的了。