一篇覆盖官方文档全部内容、由浅入深、图文并茂的完整教程
目录
- 什么是 NestJS?
- 核心架构思想
- 快速上手:5分钟启动第一个项目
- 核心构建块详解
- Controllers(控制器)
- Providers(提供者)
- Modules(模块)
- 请求处理管道
- Middleware(中间件)
- Pipes(管道)
- Guards(守卫)
- Interceptors(拦截器)
- Exception Filters(异常过滤器)
- 依赖注入进阶
- 数据库集成实战
- 安全:认证与授权
- 配置管理
- 微服务架构
- 测试策略
- 进阶技巧与最佳实践
- 完整项目实战示例
1. 什么是 NestJS?
NestJS 是一个用于构建高效、可扩展 Node.js 服务端应用的渐进式框架。它使用 TypeScript 构建,融合了 OOP(面向对象)、FP(函数式)、FRP(响应式)三种编程思想。
最重要的是,它在 Express/Fastify 之上提供了一套强大的架构层,这正是 Node.js 生态长期以来所缺失的东西。
NestJS 哲学:
架构 = 可测试性 + 可扩展性 + 低耦合 + 易维护
技术栈全景
┌──────────────────────────────────────────────────────────┐
│ NestJS 框架 │
│ │
│ TypeScript + Decorators + DI Container │
│ │
├──────────────────────────────────────────────────────────┤
│ HTTP 适配层(可选) │
│ Express (默认) | Fastify (高性能) │
├──────────────────────────────────────────────────────────┤
│ Node.js 运行时 │
└──────────────────────────────────────────────────────────┘
对比其他框架:
| 框架 | 语言 | 架构约束 | 学习曲线 | 企业级 |
|---|---|---|---|---|
| Express | JS/TS | 无 | 低 | 需自建 |
| Fastify | JS/TS | 无 | 低 | 需自建 |
| NestJS | TS | 强(Angular风格) | 中 | 开箱即用 |
| Spring Boot | Java | 强 | 高 | ✅ |
2. 核心架构思想
NestJS 的架构灵感来源于 Angular,它解决了以下关键问题:在 Node.js 项目中,如何强制实施一套清晰、可维护的模块化架构。
整体架构图
┌─────────────────────────────────────────────────────────────────┐
│ Client Request │
└──────────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Middleware Layer │
│ (日志、CORS、Body解析、Session 等) │
└──────────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Guards Layer │
│ (认证、授权、权限检查) │
└──────────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Interceptors (Pre) │
│ (日志记录、请求转换、缓存检查) │
└──────────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Pipes Layer │
│ (数据验证、类型转换、数据清洗) │
└──────────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Controller Layer │
│ (路由匹配、请求处理、响应返回) │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Service Layer │ │
│ │ (业务逻辑、数据处理、外部调用) │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ Repository Layer │ │ │
│ │ │ (数据库操作、数据持久化) │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└──────────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Interceptors (Post) │
│ (响应转换、错误映射、日志完成) │
└──────────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Exception Filters │
│ (统一错误处理、错误格式化) │
└──────────────────────────────┬──────────────────────────────────┘
│
▼
Client Response
记忆要点:请求进入时的处理顺序
Middleware → Guards → Interceptors(前) → Pipes → Controller/Handler
← Interceptors(后) ← Exception Filters ← (异常发生时)
3. 快速上手
安装与创建项目
# 全局安装 NestJS CLI
npm i -g @nestjs/cli
# 创建新项目
nest new my-project
# 进入目录并启动
cd my-project
npm run start:dev
访问 http://localhost:3000,你会看到 Hello World!。
项目初始结构
src/
├── app.controller.ts # 根控制器(处理 HTTP 请求)
├── app.controller.spec.ts # 控制器单元测试
├── app.module.ts # 根模块(应用入口)
├── app.service.ts # 根服务(业务逻辑)
└── main.ts # 启动文件
main.ts — 应用启动点:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
常用 CLI 命令速查
nest g module users # 生成模块
nest g controller users # 生成控制器
nest g service users # 生成服务
nest g resource users # 🚀 一键生成完整 CRUD(推荐!)
nest g pipe validation # 生成管道
nest g guard auth # 生成守卫
nest g interceptor logging # 生成拦截器
nest g filter http-exception # 生成异常过滤器
4. 核心构建块详解
4.1 Controllers(控制器)
控制器负责接收 HTTP 请求并返回响应,是应用的入口层。
HTTP 请求
│
▼
┌──────────────────────────────────┐
│ @Controller('cats') │
│ │
│ @Get() findAll() │ GET /cats
│ @Get(':id') findOne() │ GET /cats/1
│ @Post() create() │ POST /cats
│ @Put(':id') update() │ PUT /cats/1
│ @Delete(':id') remove() │ DELETE /cats/1
└──────────────────────────────────┘
简单示例
import { Controller, Get, Post, Body, Param, Delete, Put } from '@nestjs/common';
@Controller('cats') // 路由前缀:/cats
export class CatsController {
@Get()
findAll(): string {
return 'This returns all cats';
}
@Get(':id')
findOne(@Param('id') id: string): string {
return `This returns cat #${id}`;
}
@Post()
create(@Body() createCatDto: CreateCatDto) {
return 'This creates a new cat';
}
@Put(':id')
update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
return `This updates cat #${id}`;
}
@Delete(':id')
remove(@Param('id') id: string) {
return `This removes cat #${id}`;
}
}
常用参数装饰器对照表
| 装饰器 | 对应 Express 属性 | 用途 |
|---|---|---|
@Req() | req | 完整请求对象 |
@Res() | res | 完整响应对象 |
@Param('id') | req.params.id | 路径参数 |
@Body() | req.body | 请求体 |
@Query('page') | req.query.page | 查询参数 |
@Headers('auth') | req.headers.auth | 请求头 |
@Ip() | req.ip | 客户端 IP |
进阶:HTTP 装饰器
@Controller('cats')
export class CatsController {
@Post()
@HttpCode(204) // 自定义状态码
@Header('Cache-Control', 'no-store') // 自定义响应头
create() {
return 'Created';
}
@Get('docs')
@Redirect('https://docs.nestjs.com', 302) // 重定向
getDocs(@Query('version') version: string) {
if (version === '5') {
return { url: 'https://docs.nestjs.com/v5/' }; // 动态重定向
}
}
}
4.2 Providers(提供者)
提供者是 NestJS 中最核心的概念之一。任何带有 @Injectable() 装饰器的类都可以成为提供者(服务、仓库、工厂等),并通过依赖注入系统来使用。
提供者类型:
@Injectable() @Injectable() @Injectable()
class CatsService {} class CatsRepository {} class EmailService {}
│ │ │
└───────────────────────┴───────────────────────┘
│
NestJS IoC Container(控制反转容器)
│
┌───────────┴───────────┐
│ 自动管理实例创建 │
│ 默认单例模式共享 │
│ 自动解析依赖关系 │
└───────────────────────┘
创建服务(最常见的提供者)
// cats.service.ts
import { Injectable } from '@nestjs/common';
export interface Cat {
name: string;
age: number;
breed: string;
}
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
findOne(id: number): Cat {
return this.cats[id];
}
}
在控制器中注入服务
// cats.controller.ts
@Controller('cats')
export class CatsController {
// 通过构造函数注入 ✅ 推荐方式
constructor(private readonly catsService: CatsService) {}
@Get()
findAll(): Cat[] {
return this.catsService.findAll(); // 调用服务方法
}
}
进阶:可选依赖
import { Injectable, Optional, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
// 可选依赖:如果没有提供则为 undefined
constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}
4.3 Modules(模块)
模块是 NestJS 组织代码的基本单位,每个模块封装一组相关功能。
模块结构图:
┌──────────────────────────────────────────────────────────────┐
│ AppModule (根模块) │
│ │
│ imports: [UsersModule, CatsModule, DatabaseModule] │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ UsersModule │ │ CatsModule │ │
│ │ │ │ │ │
│ │ controllers: │ │ controllers: │ │
│ │ [UsersCtrl] │ │ [CatsCtrl] │ │
│ │ │ │ │ │
│ │ providers: │ │ providers: │ │
│ │ [UsersService] │ │ [CatsService] │ │
│ │ │ │ │ │
│ │ exports: │ │ exports: │ │
│ │ [UsersService] │ │ [CatsService] │ │
│ └─────────────────┘ └─────────────────┘ │
└──────────────────────────────────────────────────────────────┘
@Module() 装饰器四大属性
@Module({
imports: [], // 导入其他模块,使用其导出的提供者
controllers: [], // 注册控制器
providers: [], // 注册提供者(在本模块内可用)
exports: [], // 导出提供者(让其他模块可以使用)
})
示例:功能模块
// cats/cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService], // 导出后其他模块可以使用 CatsService
})
export class CatsModule {}
// app.module.ts(根模块)
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule], // 导入 CatsModule
})
export class AppModule {}
全局模块
import { Module, Global } from '@nestjs/common';
@Global() // 全局模块,不需要在其他地方 import
@Module({
providers: [DatabaseService, ConfigService],
exports: [DatabaseService, ConfigService],
})
export class CoreModule {}
动态模块(高级)
动态模块允许在运行时根据配置创建不同的模块实例,最典型的例子是数据库模块:
@Module({})
export class DatabaseModule {
// forRoot 用于全局配置(通常在 AppModule 中调用)
static forRoot(entities = [], options?): DynamicModule {
const providers = createDatabaseProviders(options, entities);
return {
module: DatabaseModule,
providers: providers,
exports: providers,
};
}
// forFeature 用于功能模块中注册特定实体
static forFeature(entities = []): DynamicModule {
// ...
}
}
// 使用方式:
@Module({
imports: [DatabaseModule.forRoot([User, Cat])],
})
export class AppModule {}
5. 请求处理管道
以下是五大请求处理机制的详细说明,理解它们的执行顺序是关键:
请求处理完整流程:
HTTP Request
│
▼
[1] Middleware ──→ next() ──→ ...(全局/路由级)
│
▼
[2] Guards ──→ true/false 决定是否继续
│
▼
[3] Interceptors ──→ 在 handle() 调用前执行
│
▼
[4] Pipes ──→ 转换和验证参数
│
▼
[5] Route Handler ──→ 执行控制器方法
│
▼
[5] Interceptors ──→ 在 handle() 调用后执行(响应处理)
│
▼
[如果抛出异常]
Exception Filters ──→ 捕获并处理异常
│
▼
HTTP Response
5.1 Middleware(中间件)
中间件在路由处理之前执行,是最先接触到请求的层。
// 类形式(推荐,可注入依赖)
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) {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next(); // 必须调用,否则请求会卡住!
}
}
// 函数形式(简洁,适合无依赖的场景)
export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`Request...`);
next();
}
注册中间件:
// app.module.ts
@Module({ imports: [CatsModule] })
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware) // 应用中间件
.exclude( // 排除某些路由
{ path: 'cats', method: RequestMethod.GET },
'cats/(.*)',
)
.forRoutes(CatsController); // 应用到指定控制器
// 多个中间件按顺序执行
consumer
.apply(cors(), helmet(), logger)
.forRoutes('*');
}
}
5.2 Pipes(管道)
管道有两个核心职责:数据转换和数据验证。
Pipes 工作原理:
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
│
▼
输入: "123" (string)
│
[ParseIntPipe]
parseInt("123")
│
▼
输出: 123 (number) ✅
如果输入 "abc":
parseInt("abc") = NaN
│
▼
抛出 BadRequestException ❌
}
内置管道一览
| 管道 | 功能 |
|---|---|
ValidationPipe | 基于 class-validator 验证 DTO |
ParseIntPipe | 字符串转整数 |
ParseFloatPipe | 字符串转浮点数 |
ParseBoolPipe | 字符串转布尔值 |
ParseArrayPipe | 字符串转数组 |
ParseUUIDPipe | 验证 UUID 格式 |
ParseEnumPipe | 验证枚举值 |
ParseDatePipe | 字符串转日期 |
DefaultValuePipe | 提供默认值 |
简单示例:内置管道
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.catsService.findOne(id); // id 已确保是数字
}
@Get()
async findAll(
@Query('active', new DefaultValuePipe(false), ParseBoolPipe) active: boolean,
@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
) {
return this.catsService.findAll({ active, page });
}
进阶示例:使用 ValidationPipe + class-validator
npm i class-validator class-transformer
// create-cat.dto.ts
import { IsString, IsInt, IsOptional, Min, Max } from 'class-validator';
export class CreateCatDto {
@IsString()
name: string;
@IsInt()
@Min(0)
@Max(30)
age: number;
@IsString()
@IsOptional()
breed?: string;
}
// main.ts —— 全局启用 ValidationPipe
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // 过滤 DTO 中未定义的字段
forbidNonWhitelisted: true, // 遇到未知字段时报错
transform: true, // 自动类型转换
transformOptions: {
enableImplicitConversion: true,
},
}));
await app.listen(3000);
}
高级示例:自定义管道
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { ZodSchema } from 'zod';
@Injectable()
export class ZodValidationPipe implements PipeTransform {
constructor(private schema: ZodSchema) {}
transform(value: unknown, metadata: ArgumentMetadata) {
try {
return this.schema.parse(value);
} catch (error) {
throw new BadRequestException('Validation failed');
}
}
}
// 使用:
import { z } from 'zod';
const createCatSchema = z.object({
name: z.string(),
age: z.number().min(0).max(30),
breed: z.string().optional(),
});
@Post()
@UsePipes(new ZodValidationPipe(createCatSchema))
create(@Body() createCatDto: CreateCatDto) {
return this.catsService.create(createCatDto);
}
5.3 Guards(守卫)
守卫决定请求是否有权访问某个路由,最常见的应用是认证和授权。
Guards 决策流程:
HTTP Request → Guard.canActivate()
│
┌──────┴──────┐
│ │
true false
│ │
▼ ▼
继续处理 403 Forbidden
(下一层) (拒绝请求)
示例:JWT 认证守卫
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private readonly jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) throw new UnauthorizedException();
try {
const payload = await this.jwtService.verifyAsync(token);
request['user'] = payload; // 将用户信息挂载到请求上
} catch {
throw new UnauthorizedException();
}
return true;
}
private extractTokenFromHeader(request: any): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
示例:基于角色的权限守卫
// roles.decorator.ts
import { Reflector } from '@nestjs/core';
export const Roles = Reflector.createDecorator<string[]>();
// roles.guard.ts
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// 获取路由所需的角色
const requiredRoles = this.reflector.get(Roles, context.getHandler());
if (!requiredRoles) return true; // 没有角色限制,放行
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some(role => user?.roles?.includes(role));
}
}
// 在控制器中使用:
@Controller('admin')
@UseGuards(AuthGuard, RolesGuard) // 先认证,再授权
export class AdminController {
@Get('users')
@Roles(['admin', 'superadmin']) // 只有 admin 或 superadmin 可访问
getUsers() {
return this.usersService.findAll();
}
}
守卫注册范围:
// 方法级别
@UseGuards(AuthGuard)
@Get('profile')
getProfile() {}
// 控制器级别
@Controller('cats')
@UseGuards(AuthGuard)
export class CatsController {}
// 全局级别(推荐通过模块注册以支持 DI)
@Module({
providers: [
{ provide: APP_GUARD, useClass: AuthGuard },
],
})
export class AppModule {}
5.4 Interceptors(拦截器)
拦截器基于 AOP(面向切面编程)思想,可以在方法执行前后绑定额外逻辑。
拦截器执行模型(使用 RxJS Observable):
Request
│
▼
intercept(context, next) {
// 前置逻辑
console.log('Before...'); ──→ 执行
return next.handle().pipe( ──→ 调用实际 Handler
tap(() => console.log('After')) ──→ Handler 执行后
);
}
示例集合
// 1. 日志拦截器
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const now = Date.now();
console.log('Before...');
return next.handle().pipe(
tap(() => console.log(`After... ${Date.now() - now}ms`)),
);
}
}
// 2. 响应转换拦截器(统一包装响应格式)
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, { data: T }> {
intercept(context: ExecutionContext, next: CallHandler): Observable<{ data: T }> {
return next.handle().pipe(
map(data => ({ data, success: true, timestamp: new Date().toISOString() }))
);
}
}
// 原始响应: { id: 1, name: 'Tom' }
// 包装后: { data: { id: 1, name: 'Tom' }, success: true, timestamp: '...' }
// 3. 缓存拦截器
@Injectable()
export class CacheInterceptor implements NestInterceptor {
private cache = new Map<string, any>();
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const cacheKey = request.url;
if (this.cache.has(cacheKey)) {
return of(this.cache.get(cacheKey)); // 命中缓存,直接返回
}
return next.handle().pipe(
tap(data => this.cache.set(cacheKey, data))
);
}
}
// 4. 超时拦截器
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
timeout(5000),
catchError(err => {
if (err instanceof TimeoutError) {
return throwError(() => new RequestTimeoutException());
}
return throwError(() => err);
}),
);
}
}
5.5 Exception Filters(异常过滤器)
NestJS 内置了全局异常处理层,当抛出 HttpException 时会自动返回格式化的 JSON 响应。
异常处理流程:
任意层抛出异常
│
▼
是 HttpException?
┌────┴────┐
Yes No
│ │
▼ ▼
返回对应 返回 500
HTTP状态码 Internal Server Error
自定义 ExceptionFilter 可以捕获并处理任意类型的异常
内置 HTTP 异常
// 常用内置异常
throw new BadRequestException('无效的请求参数'); // 400
throw new UnauthorizedException('请先登录'); // 401
throw new ForbiddenException('权限不足'); // 403
throw new NotFoundException('资源不存在'); // 404
throw new ConflictException('资源已存在'); // 409
throw new InternalServerErrorException('服务器错误'); // 500
// 完整示例
@Get()
async findAll() {
const cats = await this.catsService.findAll();
if (!cats || cats.length === 0) {
throw new NotFoundException('No cats found');
}
return cats;
}
自定义异常类
// custom-exception.ts
export class BusinessException extends HttpException {
constructor(message: string, code: string, httpStatus: number) {
super({ message, code, success: false }, httpStatus);
}
}
// 使用
throw new BusinessException('用户余额不足', 'INSUFFICIENT_BALANCE', HttpStatus.BAD_REQUEST);
自定义全局异常过滤器
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch() // 捕获所有异常
export class GlobalExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message = exception instanceof HttpException
? exception.message
: 'Internal server error';
// 统一的错误响应格式
response.status(status).json({
success: false,
statusCode: status,
message,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
// 注册为全局过滤器
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new GlobalExceptionFilter());
await app.listen(3000);
}
6. 依赖注入进阶
自定义提供者的四种形式
标准简写:
providers: [CatsService]
完整写法(等价于上面):
providers: [{ provide: CatsService, useClass: CatsService }]
这里的 provide 就是"令牌"(Token),可以是类、字符串或 Symbol
// 1. useValue —— 注入常量值或 Mock 对象
providers: [
{
provide: CatsService,
useValue: mockCatsService, // 测试时用 Mock 替换真实服务
},
{
provide: 'API_KEY', // 字符串 Token
useValue: 'my-secret-key',
},
]
// 2. useClass —— 根据条件选择不同的实现类
providers: [
{
provide: ConfigService,
useClass: process.env.NODE_ENV === 'development'
? DevConfigService
: ProdConfigService,
},
]
// 3. useFactory —— 工厂函数(可注入其他依赖,支持异步)
providers: [
{
provide: 'DATABASE_CONNECTION',
useFactory: async (configService: ConfigService) => {
const connection = await createDatabaseConnection({
host: configService.get('DB_HOST'),
port: configService.get('DB_PORT'),
});
return connection;
},
inject: [ConfigService], // 注入到工厂函数的依赖
},
]
// 4. useExisting —— 为现有提供者创建别名
providers: [
LoggerService,
{
provide: 'LOGGER',
useExisting: LoggerService, // 别名,引用同一个实例
},
]
注入自定义令牌(Token):
// 注入字符串 Token
@Injectable()
export class CatsRepository {
constructor(
@Inject('DATABASE_CONNECTION') private readonly db: DatabaseConnection,
@Inject('API_KEY') private readonly apiKey: string,
) {}
}
7. 数据库集成实战
TypeORM 集成(最推荐)
npm install --save @nestjs/typeorm typeorm pg # PostgreSQL
# 或
npm install --save @nestjs/typeorm typeorm mysql2 # MySQL
TypeORM + NestJS 架构:
Controller ──→ Service ──→ Repository ──→ Database
│
@InjectRepository(User)
private userRepo: Repository<User>
│
userRepo.find()
userRepo.save()
userRepo.delete()
完整示例
// user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 50 })
firstName: string;
@Column({ length: 50 })
lastName: string;
@Column({ unique: true })
email: string;
@Column({ select: false }) // 默认查询不返回密码
password: string;
@Column({ default: true })
isActive: boolean;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
// 2. 配置模块(app.module.ts)
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get('DB_HOST', 'localhost'),
port: configService.get<number>('DB_PORT', 5432),
username: configService.get('DB_USER', 'postgres'),
password: configService.get('DB_PASSWORD', ''),
database: configService.get('DB_NAME', 'nestdb'),
autoLoadEntities: true, // 自动加载所有通过 forFeature 注册的实体
synchronize: true, // ⚠️ 仅开发环境使用,生产用 migrations
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}
// 3. 功能模块(users.module.ts)
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
@Module({
imports: [TypeOrmModule.forFeature([User])], // 注册 User 仓库
providers: [UsersService],
controllers: [UsersController],
exports: [UsersService],
})
export class UsersModule {}
// 4. 服务层(users.service.ts)
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly usersRepo: Repository<User>,
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
// 检查邮箱是否已存在
const existing = await this.usersRepo.findOne({
where: { email: createUserDto.email }
});
if (existing) throw new ConflictException('Email already exists');
const user = this.usersRepo.create(createUserDto);
return this.usersRepo.save(user);
}
findAll(): Promise<User[]> {
return this.usersRepo.find({ where: { isActive: true } });
}
async findOne(id: number): Promise<User> {
const user = await this.usersRepo.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.usersRepo.save(user);
}
async remove(id: number): Promise<void> {
const user = await this.findOne(id);
await this.usersRepo.remove(user);
}
// 使用 QueryBuilder 进行复杂查询
async findActiveUsersWithPagination(page: number, limit: number) {
const [data, total] = await this.usersRepo
.createQueryBuilder('user')
.where('user.isActive = :isActive', { isActive: true })
.orderBy('user.createdAt', 'DESC')
.skip((page - 1) * limit)
.take(limit)
.getManyAndCount();
return {
data,
total,
page,
lastPage: Math.ceil(total / limit),
};
}
}
// 5. DTO 定义(dto/create-user.dto.ts)
import { IsEmail, IsString, MinLength, MaxLength, IsOptional } from 'class-validator';
export class CreateUserDto {
@IsString()
@MaxLength(50)
firstName: string;
@IsString()
@MaxLength(50)
lastName: string;
@IsEmail()
email: string;
@IsString()
@MinLength(8)
password: string;
}
事务处理(高级)
@Injectable()
export class OrderService {
constructor(private dataSource: DataSource) {}
async createOrderWithPayment(orderData: any) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
// 操作1:创建订单
const order = queryRunner.manager.create(Order, orderData);
await queryRunner.manager.save(order);
// 操作2:扣减库存
await queryRunner.manager.decrement(
Product,
{ id: orderData.productId },
'stock',
orderData.quantity
);
// 操作3:创建支付记录
const payment = queryRunner.manager.create(Payment, { orderId: order.id });
await queryRunner.manager.save(payment);
await queryRunner.commitTransaction(); // 全部成功,提交
return order;
} catch (err) {
await queryRunner.rollbackTransaction(); // 任何失败,回滚
throw err;
} finally {
await queryRunner.release(); // 释放连接
}
}
}
8. 安全:认证与授权
JWT 认证完整实现
JWT 认证流程:
┌──────────┐ ┌──────────────┐
│ Client │ │ NestJS │
└──────────┘ └──────────────┘
│ │
│ POST /auth/login │
│ { username, password } ──────────▶ │
│ │ 验证凭证
│ │ 生成 JWT
│ ◀────────────────────────────────── │
│ { access_token: "eyJhbG..." } │
│ │
│ GET /profile │
│ Authorization: Bearer eyJhbG... ───▶ │
│ │ 验证 JWT
│ │ 解析 payload
│ ◀────────────────────────────────── │
│ { userId: 1, username: "john" } │
安装依赖
npm install --save @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt
npm install --save-dev @types/passport-jwt @types/bcrypt
完整实现
// auth/constants.ts
export const jwtConstants = {
// ⚠️ 生产环境请使用环境变量!
secret: process.env.JWT_SECRET || 'your-very-secret-key',
expiresIn: '7d',
};
// users/users.service.ts(简化版)
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User) private usersRepo: Repository<User>
) {}
async findByUsername(username: string): Promise<User | null> {
return this.usersRepo.findOne({
where: { username },
select: ['id', 'username', 'password', 'roles'], // 包含 password 用于验证
});
}
}
// auth/auth.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async signIn(username: string, password: string) {
const user = await this.usersService.findByUsername(username);
if (!user) throw new UnauthorizedException('Invalid credentials');
// 使用 bcrypt 比对密码
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) throw new UnauthorizedException('Invalid credentials');
const payload = { sub: user.id, username: user.username, roles: user.roles };
return {
access_token: await this.jwtService.signAsync(payload),
user: { id: user.id, username: user.username },
};
}
async hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, 10);
}
}
// auth/auth.guard.ts
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { IS_PUBLIC_KEY } from './decorators/public.decorator';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private jwtService: JwtService,
private reflector: Reflector,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
// 检查是否标记为公开路由
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization?.split(' ')[1];
if (!token) throw new UnauthorizedException();
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: jwtConstants.secret,
});
request['user'] = payload;
} catch {
throw new UnauthorizedException('Token invalid or expired');
}
return true;
}
}
// auth/decorators/public.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
// auth/auth.module.ts
@Module({
imports: [
UsersModule,
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
global: true,
secret: configService.get('JWT_SECRET'),
signOptions: { expiresIn: '7d' },
}),
inject: [ConfigService],
}),
],
providers: [
AuthService,
// 全局注册守卫
{ provide: APP_GUARD, useClass: AuthGuard },
{ provide: APP_GUARD, useClass: RolesGuard },
],
controllers: [AuthController],
exports: [AuthService],
})
export class AuthModule {}
// auth/auth.controller.ts
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Public() // 标记为公开,不需要 JWT
@Post('login')
@HttpCode(HttpStatus.OK)
signIn(@Body() signInDto: SignInDto) {
return this.authService.signIn(signInDto.username, signInDto.password);
}
@Get('profile') // 需要 JWT(默认)
getProfile(@Request() req) {
return req.user;
}
}
// 在业务控制器中使用角色
@Controller('admin')
@Roles(['admin']) // 整个控制器需要 admin 角色
export class AdminController {
@Get('stats')
@Roles(['admin', 'analyst']) // 方法级别覆盖
getStats() {
return this.statsService.get();
}
@Delete('users/:id')
@Roles(['superadmin']) // 高危操作只允许超级管理员
deleteUser(@Param('id') id: string) {
return this.usersService.remove(+id);
}
}
9. 配置管理
使用 @nestjs/config
npm i --save @nestjs/config
// app.module.ts
import { ConfigModule } from '@nestjs/config';
import configuration from './config/configuration';
import * as Joi from 'joi';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true, // 全局可用,不需要在其他模块 import
envFilePath: [
`.env.${process.env.NODE_ENV}`, // 先尝试加载环境特定文件
'.env', // 回退到默认文件
],
load: [configuration], // 加载自定义配置文件
cache: true, // 缓存 process.env 访问提升性能
// 使用 Joi 验证必要的环境变量
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test')
.default('development'),
PORT: Joi.number().default(3000),
DATABASE_URL: Joi.string().required(),
JWT_SECRET: Joi.string().min(32).required(),
}),
}),
],
})
export class AppModule {}
// config/configuration.ts
export default () => ({
port: parseInt(process.env.PORT, 10) || 3000,
database: {
host: process.env.DATABASE_HOST || 'localhost',
port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
name: process.env.DATABASE_NAME || 'mydb',
username: process.env.DATABASE_USER || 'postgres',
password: process.env.DATABASE_PASSWORD || '',
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || '7d',
},
redis: {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT, 10) || 6379,
},
});
// 在服务中使用 ConfigService
@Injectable()
export class AppService {
constructor(private configService: ConfigService) {}
getConfig() {
// 访问简单环境变量
const port = this.configService.get<number>('port');
// 访问嵌套配置
const dbHost = this.configService.get<string>('database.host');
// 带默认值
const timeout = this.configService.get<number>('timeout', 5000);
return { port, dbHost, timeout };
}
}
// 命名空间配置(更推荐的组织方式)
// config/database.config.ts
import { registerAs } from '@nestjs/config';
export default registerAs('database', () => ({
host: process.env.DATABASE_HOST || 'localhost',
port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
name: process.env.DATABASE_NAME || 'mydb',
}));
// 使用命名空间注入(完全类型安全)
import { ConfigType } from '@nestjs/config';
import databaseConfig from './config/database.config';
@Injectable()
export class DatabaseService {
constructor(
@Inject(databaseConfig.KEY)
private dbConfig: ConfigType<typeof databaseConfig>,
) {
console.log(dbConfig.host); // 完全类型提示!
}
}
10. 微服务架构
NestJS 内置了强大的微服务支持,同样的 Guards、Pipes、Interceptors 都可以用于微服务。
微服务通信架构:
┌────────────────────┐ TCP/Redis/NATS ┌────────────────────┐
│ API Gateway │ ◀──────────────────────▶ │ Users Service │
│ (HTTP Server) │ │ (Microservice) │
└──────────┬─────────┘ └────────────────────┘
│
│ TCP/Redis/NATS
▼
┌────────────────────┐ TCP/Redis/NATS ┌────────────────────┐
│ Orders Service │ ◀──────────────────────▶ │ Products Service │
│ (Microservice) │ │ (Microservice) │
└────────────────────┘ └────────────────────┘
创建微服务
// main.ts(微服务启动文件)
import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
options: { host: '0.0.0.0', port: 3001 },
},
);
await app.listen();
console.log('Microservice is listening on port 3001');
}
bootstrap();
// 微服务控制器:使用 @MessagePattern 处理请求-响应
@Controller()
export class UsersController {
constructor(private usersService: UsersService) {}
// 请求-响应模式
@MessagePattern({ cmd: 'get_user' })
getUser(@Payload() data: { id: number }) {
return this.usersService.findOne(data.id);
}
@MessagePattern({ cmd: 'create_user' })
async createUser(@Payload() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
// 事件驱动模式(单向,不等待响应)
@EventPattern('user_registered')
async handleUserRegistered(@Payload() data: { userId: number; email: string }) {
await this.emailService.sendWelcomeEmail(data.email);
console.log(`Welcome email sent to ${data.email}`);
}
}
// API Gateway:作为微服务的客户端
@Module({
imports: [
ClientsModule.registerAsync([
{
name: 'USERS_SERVICE',
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
transport: Transport.TCP,
options: {
host: configService.get('USERS_SERVICE_HOST', 'localhost'),
port: configService.get<number>('USERS_SERVICE_PORT', 3001),
},
}),
inject: [ConfigService],
},
]),
],
})
export class UsersGatewayModule {}
@Controller('users')
export class UsersGatewayController {
constructor(
@Inject('USERS_SERVICE') private usersClient: ClientProxy,
) {}
@Get(':id')
getUser(@Param('id') id: string) {
// send() 用于请求-响应,返回 Observable
return this.usersClient.send<User>({ cmd: 'get_user' }, { id: +id });
}
@Post()
createUser(@Body() createUserDto: CreateUserDto) {
return this.usersClient.send<User>({ cmd: 'create_user' }, createUserDto);
}
@Post('notify')
notifyUser(@Body() data: any) {
// emit() 用于事件发布,不等待响应
this.usersClient.emit('user_registered', data);
return { message: 'Event emitted' };
}
}
混合应用(HTTP + 微服务)
// 同一个应用同时处理 HTTP 请求和微服务消息
async function bootstrap() {
// 创建 HTTP 应用
const app = await NestFactory.create(AppModule);
// 附加微服务
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.TCP,
options: { port: 3001 },
});
await app.startAllMicroservices();
await app.listen(3000);
console.log('HTTP: 3000, Microservice: 3001');
}
11. 测试策略
NestJS 提供了与 Jest 深度集成的测试工具,支持单元测试、集成测试和 E2E 测试。
测试金字塔:
┌─────────────┐
│ E2E 测试 │ 少量,测整体流程
│ (慢,真实) │
┌┴─────────────┴┐
│ 集成测试 │ 适量,测模块间协作
│ (中,部分 Mock)│
┌┴───────────────┴┐
│ 单元测试 │ 大量,测单个类/函数
│ (快,完全 Mock) │
└─────────────────┘
单元测试
// cats.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { CatsService } from './cats.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Cat } from './cat.entity';
import { Repository } from 'typeorm';
describe('CatsService', () => {
let service: CatsService;
let repo: Repository<Cat>;
// Mock Repository
const mockCatRepository = {
find: jest.fn(),
findOne: jest.fn(),
save: jest.fn(),
create: jest.fn(),
delete: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
CatsService,
{
provide: getRepositoryToken(Cat),
useValue: mockCatRepository, // 替换真实仓库为 Mock
},
],
}).compile();
service = module.get<CatsService>(CatsService);
repo = module.get<Repository<Cat>>(getRepositoryToken(Cat));
});
afterEach(() => jest.clearAllMocks());
describe('findAll', () => {
it('should return an array of cats', async () => {
const mockCats = [
{ id: 1, name: 'Tom', age: 3 },
{ id: 2, name: 'Jerry', age: 2 },
];
mockCatRepository.find.mockResolvedValue(mockCats);
const result = await service.findAll();
expect(result).toEqual(mockCats);
expect(mockCatRepository.find).toHaveBeenCalledTimes(1);
});
});
describe('findOne', () => {
it('should return a cat when found', async () => {
const mockCat = { id: 1, name: 'Tom', age: 3 };
mockCatRepository.findOne.mockResolvedValue(mockCat);
const result = await service.findOne(1);
expect(result).toEqual(mockCat);
});
it('should throw NotFoundException when cat not found', async () => {
mockCatRepository.findOne.mockResolvedValue(null);
await expect(service.findOne(999)).rejects.toThrow(NotFoundException);
});
});
});
E2E 测试
// test/cats.e2e-spec.ts
import * as request from 'supertest';
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import { AppModule } from '../src/app.module';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Cat } from '../src/cats/cat.entity';
describe('CatsController (e2e)', () => {
let app: INestApplication;
const mockCatsRepository = {
find: jest.fn().mockResolvedValue([
{ id: 1, name: 'Tom', age: 3, breed: 'Persian' },
]),
findOne: jest.fn().mockResolvedValue({ id: 1, name: 'Tom', age: 3 }),
save: jest.fn().mockImplementation(dto => Promise.resolve({ id: 1, ...dto })),
create: jest.fn().mockImplementation(dto => dto),
delete: jest.fn().mockResolvedValue({ affected: 1 }),
};
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
})
.overrideProvider(getRepositoryToken(Cat))
.useValue(mockCatsRepository)
.compile();
app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
await app.init();
});
afterAll(async () => await app.close());
it('GET /cats → 200', () => {
return request(app.getHttpServer())
.get('/cats')
.expect(200)
.expect(res => {
expect(Array.isArray(res.body)).toBe(true);
});
});
it('POST /cats → 201', () => {
return request(app.getHttpServer())
.post('/cats')
.send({ name: 'Tom', age: 3, breed: 'Persian' })
.expect(201)
.expect(res => {
expect(res.body).toHaveProperty('id');
expect(res.body.name).toBe('Tom');
});
});
it('POST /cats → 400 when invalid data', () => {
return request(app.getHttpServer())
.post('/cats')
.send({ name: 'Tom', age: 'not-a-number' }) // age 应为数字
.expect(400);
});
});
12. 进阶技巧与最佳实践
12.1 自定义装饰器
// 提取当前登录用户的装饰器
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: User) {
return user;
}
@Get('profile/id')
getProfileId(@CurrentUser('id') userId: number) {
return userId;
}
// 组合多个装饰器为一个
import { applyDecorators, UseGuards, SetMetadata } from '@nestjs/common';
import { ApiBearerAuth } from '@nestjs/swagger';
export function Auth(...roles: string[]) {
return applyDecorators(
SetMetadata('roles', roles),
UseGuards(AuthGuard, RolesGuard),
ApiBearerAuth(),
);
}
// 使用方式(非常简洁!)
@Get('admin')
@Auth('admin')
getAdminData() { ... }
12.2 生命周期钩子
@Injectable()
export class DatabaseService implements OnModuleInit, OnModuleDestroy {
private connection: Connection;
async onModuleInit() {
// 模块初始化时执行(适合建立连接)
this.connection = await createConnection();
console.log('Database connected');
}
async onModuleDestroy() {
// 模块销毁时执行(适合释放资源)
await this.connection.close();
console.log('Database connection closed');
}
}
// 应用级别的钩子
@Injectable()
export class AppService implements OnApplicationBootstrap, BeforeApplicationShutdown {
async onApplicationBootstrap() {
// 所有模块初始化完成后执行
console.log('Application is ready');
}
async beforeApplicationShutdown(signal?: string) {
// 应用关闭前执行(优雅关闭)
console.log(`Received ${signal}. Gracefully shutting down...`);
}
}
12.3 注入作用域(Scopes)
import { Injectable, Scope } from '@nestjs/common';
// 单例(默认):整个应用共享同一实例
@Injectable()
export class SingletonService {}
// 请求级:每个 HTTP 请求创建新实例
@Injectable({ scope: Scope.REQUEST })
export class RequestScopedService {
constructor(@Inject(REQUEST) private request: Request) {}
getUserId() {
return this.request['user']?.id;
}
}
// 瞬态:每次注入都创建新实例
@Injectable({ scope: Scope.TRANSIENT })
export class TransientService {}
12.4 OpenAPI(Swagger)文档
npm install --save @nestjs/swagger
// main.ts
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('My API')
.setDescription('The API documentation')
.setVersion('1.0')
.addBearerAuth() // 添加 JWT 认证
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document); // 访问 /api 查看文档
await app.listen(3000);
}
// 在 DTO 中添加 Swagger 注解
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class CreateCatDto {
@ApiProperty({ description: '猫的名字', example: 'Tom' })
@IsString()
name: string;
@ApiProperty({ description: '年龄', example: 3, minimum: 0, maximum: 30 })
@IsInt()
@Min(0)
age: number;
@ApiPropertyOptional({ description: '品种', example: 'Persian' })
@IsString()
@IsOptional()
breed?: string;
}
// 在控制器中添加 Swagger 注解
@ApiTags('cats')
@Controller('cats')
export class CatsController {
@ApiOperation({ summary: '获取所有猫' })
@ApiResponse({ status: 200, description: '成功', type: [Cat] })
@Get()
findAll() { ... }
@ApiOperation({ summary: '创建猫' })
@ApiResponse({ status: 201, description: '创建成功', type: Cat })
@ApiResponse({ status: 400, description: '参数错误' })
@Post()
create(@Body() createCatDto: CreateCatDto) { ... }
}
12.5 性能优化技巧
// 1. 使用 Fastify 替代 Express(性能约提升 2-3x)
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter({ logger: true }),
);
await app.listen(3000, '0.0.0.0');
// 2. 启用 SWC 编译器(比 tsc 快 20 倍!)
// nest start -b swc
// nest build -b swc
// 3. 启用响应压缩
import * as compression from 'compression';
app.use(compression());
// 4. 使用 Helmet 设置安全 HTTP 头
import helmet from 'helmet';
app.use(helmet());
// 5. 启用 CORS
app.enableCors({
origin: ['https://myapp.com', 'https://admin.myapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true,
});
// 6. 全局前缀
app.setGlobalPrefix('api/v1');
// 所有路由变为 /api/v1/cats、/api/v1/users 等
13. 完整项目实战示例
博客系统 API(综合示例)
下面是一个整合了上述所有概念的真实项目结构:
src/
├── main.ts
├── app.module.ts
├── config/
│ ├── configuration.ts
│ └── database.config.ts
├── common/
│ ├── decorators/
│ │ ├── current-user.decorator.ts
│ │ ├── public.decorator.ts
│ │ └── roles.decorator.ts
│ ├── filters/
│ │ └── global-exception.filter.ts
│ ├── guards/
│ │ ├── auth.guard.ts
│ │ └── roles.guard.ts
│ ├── interceptors/
│ │ ├── logging.interceptor.ts
│ │ └── transform.interceptor.ts
│ └── pipes/
│ └── validation.pipe.ts
├── auth/
│ ├── auth.module.ts
│ ├── auth.controller.ts
│ └── auth.service.ts
├── users/
│ ├── users.module.ts
│ ├── users.controller.ts
│ ├── users.service.ts
│ ├── user.entity.ts
│ └── dto/
│ ├── create-user.dto.ts
│ └── update-user.dto.ts
└── posts/
├── posts.module.ts
├── posts.controller.ts
├── posts.service.ts
├── post.entity.ts
└── dto/
├── create-post.dto.ts
└── query-posts.dto.ts
posts/post.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, CreateDateColumn } from 'typeorm';
import { User } from '../users/user.entity';
@Entity('posts')
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column('text')
content: string;
@Column({ default: false })
isPublished: boolean;
@ManyToOne(() => User, user => user.posts, { eager: true })
author: User;
@CreateDateColumn()
createdAt: Date;
}
posts/dto/query-posts.dto.ts
import { IsOptional, IsBoolean, IsInt, Min, Max } from 'class-validator';
import { Transform, Type } from 'class-transformer';
export class QueryPostsDto {
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
page?: number = 1;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
@Max(100)
limit?: number = 10;
@IsOptional()
@Transform(({ value }) => value === 'true')
@IsBoolean()
published?: boolean;
}
posts/posts.service.ts
@Injectable()
export class PostsService {
constructor(
@InjectRepository(Post)
private postsRepo: Repository<Post>,
) {}
async create(createPostDto: CreatePostDto, author: User): Promise<Post> {
const post = this.postsRepo.create({ ...createPostDto, author });
return this.postsRepo.save(post);
}
async findAll(queryDto: QueryPostsDto) {
const { page, limit, published } = queryDto;
const queryBuilder = this.postsRepo
.createQueryBuilder('post')
.leftJoinAndSelect('post.author', 'author')
.orderBy('post.createdAt', 'DESC');
if (published !== undefined) {
queryBuilder.andWhere('post.isPublished = :published', { published });
}
const [data, total] = await queryBuilder
.skip((page - 1) * limit)
.take(limit)
.getManyAndCount();
return {
data,
meta: {
total,
page,
limit,
lastPage: Math.ceil(total / limit),
},
};
}
async findOne(id: number): Promise<Post> {
const post = await this.postsRepo.findOne({
where: { id },
relations: ['author'],
});
if (!post) throw new NotFoundException(`Post #${id} not found`);
return post;
}
async update(id: number, updateDto: UpdatePostDto, userId: number): Promise<Post> {
const post = await this.findOne(id);
if (post.author.id !== userId) {
throw new ForbiddenException('You can only edit your own posts');
}
Object.assign(post, updateDto);
return this.postsRepo.save(post);
}
async remove(id: number, userId: number): Promise<void> {
const post = await this.findOne(id);
if (post.author.id !== userId) {
throw new ForbiddenException('You can only delete your own posts');
}
await this.postsRepo.remove(post);
}
async publish(id: number): Promise<Post> {
const post = await this.findOne(id);
post.isPublished = true;
return this.postsRepo.save(post);
}
}
posts/posts.controller.ts
import {
Controller, Get, Post, Put, Delete, Patch,
Body, Param, Query, ParseIntPipe,
HttpCode, HttpStatus,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { PostsService } from './posts.service';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
import { QueryPostsDto } from './dto/query-posts.dto';
import { CurrentUser } from '../common/decorators/current-user.decorator';
import { Public } from '../common/decorators/public.decorator';
import { Roles } from '../common/decorators/roles.decorator';
import { User } from '../users/user.entity';
@ApiTags('posts')
@ApiBearerAuth()
@Controller('posts')
export class PostsController {
constructor(private readonly postsService: PostsService) {}
// ✅ 公开接口:任何人都可以查看已发布文章
@Public()
@Get()
@ApiOperation({ summary: '获取文章列表' })
findAll(@Query() queryDto: QueryPostsDto) {
return this.postsService.findAll(queryDto);
}
// ✅ 公开接口:查看单篇文章
@Public()
@Get(':id')
@ApiOperation({ summary: '获取单篇文章' })
findOne(@Param('id', ParseIntPipe) id: number) {
return this.postsService.findOne(id);
}
// 🔒 需要登录:创建文章
@Post()
@ApiOperation({ summary: '创建文章' })
create(@Body() createPostDto: CreatePostDto, @CurrentUser() user: User) {
return this.postsService.create(createPostDto, user);
}
// 🔒 需要登录:更新自己的文章
@Put(':id')
@ApiOperation({ summary: '更新文章' })
update(
@Param('id', ParseIntPipe) id: number,
@Body() updatePostDto: UpdatePostDto,
@CurrentUser('id') userId: number,
) {
return this.postsService.update(id, updatePostDto, userId);
}
// 🔒 需要登录:删除自己的文章
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: '删除文章' })
remove(
@Param('id', ParseIntPipe) id: number,
@CurrentUser('id') userId: number,
) {
return this.postsService.remove(id, userId);
}
// 🔐 仅管理员:发布文章
@Patch(':id/publish')
@Roles(['admin'])
@ApiOperation({ summary: '发布文章(仅管理员)' })
publish(@Param('id', ParseIntPipe) id: number) {
return this.postsService.publish(id);
}
}
main.ts(完整启动配置)
import { NestFactory, Reflector } from '@nestjs/core';
import { ValidationPipe, ClassSerializerInterceptor } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import * as compression from 'compression';
import helmet from 'helmet';
import { AppModule } from './app.module';
import { GlobalExceptionFilter } from './common/filters/global-exception.filter';
import { TransformInterceptor } from './common/interceptors/transform.interceptor';
import { LoggingInterceptor } from './common/interceptors/logging.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: ['error', 'warn', 'log'],
});
// ── 安全中间件 ──────────────────────────────────────────────────
app.use(helmet());
app.use(compression());
app.enableCors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || '*',
credentials: true,
});
// ── 全局前缀 ────────────────────────────────────────────────────
app.setGlobalPrefix('api/v1');
// ── 全局管道(数据验证)─────────────────────────────────────────
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: { enableImplicitConversion: true },
}),
);
// ── 全局拦截器 ──────────────────────────────────────────────────
const reflector = app.get(Reflector);
app.useGlobalInterceptors(
new LoggingInterceptor(),
new TransformInterceptor(),
new ClassSerializerInterceptor(reflector), // 自动应用 @Exclude() 等序列化装饰器
);
// ── 全局异常过滤器 ──────────────────────────────────────────────
app.useGlobalFilters(new GlobalExceptionFilter());
// ── Swagger 文档 ────────────────────────────────────────────────
if (process.env.NODE_ENV !== 'production') {
const config = new DocumentBuilder()
.setTitle('Blog API')
.setDescription('博客系统接口文档')
.setVersion('1.0')
.addBearerAuth(
{ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
'access-token',
)
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api/docs', app, document, {
swaggerOptions: { persistAuthorization: true },
});
console.log('📚 Swagger docs: http://localhost:3000/api/docs');
}
// ── 优雅关闭 ────────────────────────────────────────────────────
app.enableShutdownHooks();
const port = process.env.PORT ?? 3000;
await app.listen(port);
console.log(`🚀 Application running on: http://localhost:${port}/api/v1`);
}
bootstrap();
common/interceptors/transform.interceptor.ts(统一响应格式)
import {
Injectable, NestInterceptor, ExecutionContext,
CallHandler, HttpStatus,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface StandardResponse<T> {
success: boolean;
statusCode: number;
data: T;
timestamp: string;
}
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, StandardResponse<T>>
{
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<StandardResponse<T>> {
const response = context.switchToHttp().getResponse();
return next.handle().pipe(
map(data => ({
success: true,
statusCode: response.statusCode ?? HttpStatus.OK,
data,
timestamp: new Date().toISOString(),
})),
);
}
}
附录:核心概念速查卡
装饰器速查表
HTTP 方法装饰器:
@Get('/path') @Post() @Put() @Patch() @Delete() @Options() @Head()
参数装饰器:
@Param('id') 路径参数 /users/:id
@Query('page') 查询参数 /users?page=1
@Body() 请求体 POST /users { name: 'Tom' }
@Headers('auth') 请求头
@Req() 完整 Request 对象
@Res() 完整 Response 对象(会绕过 NestJS 响应机制)
响应控制:
@HttpCode(201) 修改默认状态码
@Header('X-Key', 'val') 设置响应头
@Redirect('/other', 301) 重定向
作用域装饰器:
@UseGuards(Guard) 应用守卫
@UseInterceptors(Interceptor) 应用拦截器
@UsePipes(Pipe) 应用管道
@UseFilters(Filter) 应用异常过滤器
元数据装饰器:
@SetMetadata('key', value) 设置自定义元数据
Reflector.createDecorator() 创建类型安全的元数据装饰器
全局注册方式对比
┌──────────────────┬────────────────────────────┬────────────────────────────┐
│ 组件 │ main.ts 方式 │ 模块方式(推荐,支持DI) │
├──────────────────┼────────────────────────────┼────────────────────────────┤
│ Guard │ app.useGlobalGuards() │ { provide: APP_GUARD, │
│ │ │ useClass: AuthGuard } │
├──────────────────┼────────────────────────────┼────────────────────────────┤
│ Pipe │ app.useGlobalPipes() │ { provide: APP_PIPE, │
│ │ │ useClass: ValidationPipe}│
├──────────────────┼────────────────────────────┼────────────────────────────┤
│ Interceptor │ app.useGlobalInterceptors() │ { provide: APP_INTERCEPTOR,│
│ │ │ useClass: LoggingInter.} │
├──────────────────┼────────────────────────────┼────────────────────────────┤
│ Filter │ app.useGlobalFilters() │ { provide: APP_FILTER, │
│ │ │ useClass: ExceptionFilter}│
└──────────────────┴────────────────────────────┴────────────────────────────┘
模块通信规则
模块间通信的唯一合法方式:
┌─────────────────────────────────────────┐
│ Module A │
│ providers: [ServiceA] │
│ exports: [ServiceA] ◀── 必须显式导出 │
└───────────────────┬─────────────────────┘
│
│ 只有通过 imports 才能使用
▼
┌─────────────────────────────────────────┐
│ Module B │
│ imports: [ModuleA] ◀── 必须导入模块 │
│ providers: [ServiceB] │
│ │
│ ServiceB 现在可以注入 ServiceA │
└─────────────────────────────────────────┘
总结
学完本文,你掌握了 NestJS 的完整知识体系:
基础层:安装 → 项目结构 → Controllers → Providers → Modules
请求处理层:Middleware → Guards → Interceptors → Pipes → Exception Filters
进阶特性:自定义提供者(useValue/useClass/useFactory/useExisting)→ 动态模块 → 注入作用域 → 生命周期钩子
数据层:TypeORM 集成 → Repository 模式 → 关系映射 → 事务处理
安全层:JWT 认证 → 角色授权 → 全局守卫 + 公开路由例外
配置层:@nestjs/config → 环境变量 → Joi 验证 → 命名空间配置
微服务层:TCP/Redis/NATS 传输 → MessagePattern → EventPattern → ClientProxy
测试层:单元测试 → E2E 测试 → Mock Repository → Override Provider
NestJS 的设计哲学是:通过约束来解放创造力。它用清晰的架构模式帮你规避了大量 Node.js 项目中常见的混乱问题,让团队可以专注于业务逻辑而非架构决策。无论是小型 API 还是复杂的企业级微服务系统,NestJS 都是构建 Node.js 后端的绝佳选择。