【NestJS】15 最佳实践与学习总结
深入理解 NestJS 架构设计理念,掌握从项目结构到代码质量的全方位最佳实践,构建可维护、可扩展的企业级应用。 很长,但耐心看完一定有收获,这一个多月所有宝贵的学习经验都汇聚于此
前言 ✨
经过前面章节的学习,我们已经掌握了 NestJS 的各个核心概念:控制器、服务、模块、中间件、拦截器、管道、守卫、异常过滤器等。这些概念看起来很多,但其实都遵循同一个设计原则。
这篇文章将帮助你:
- 🎯 理解 NestJS 的整体架构:建立完整的知识体系
- 📐 掌握最佳实践:从项目结构到代码质量的全方位指南
- 🔍 深入理解设计理念:为什么 NestJS 这样设计
- 🚀 提升开发效率:避免常见陷阱,写出高质量代码
让我们开始这段总结之旅吧!
一、NestJS 架构总览 🏗️
请求处理流程:理解 NestJS 的灵魂
NestJS 的完整请求处理流程遵循严格的顺序,理解这个顺序,你就理解了 NestJS 的核心设计理念:
HTTP 请求 ↓1. Middleware(中间件) ↓2. Guard(守卫)- 认证和授权检查 ↓3. Interceptor(拦截器)- 请求前处理 ↓4. Pipe(管道)- 数据验证和转换 ↓5. Controller(控制器)- 处理请求 ↓6. Service(服务)- 业务逻辑 ↓7. 返回响应 ↓8. Interceptor(拦截器)- 响应后处理 ↓9. Exception Filter(异常过滤器)- 错误处理 ↓HTTP 响应💡 设计哲学:这个流程体现了”关注点分离”的设计思想,每一层都有明确的职责,互不干扰。
为什么要有这些层次?
1. Middleware(中间件)
- 作用:全局的请求预处理
- 典型场景:日志记录、请求计时、CORS 处理
- 执行时机:最早,在所有 NestJS 特性之前
2. Guard(守卫)
- 作用:认证和授权检查
- 典型场景:JWT 认证、角色权限验证
- 设计理念:决定请求是否应该被处理(像门卫一样)
3. Interceptor(拦截器)
- 作用:在请求前后进行处理
- 典型场景:统一响应格式、日志记录、性能监控、缓存
- 设计理念:横切关注点(Cross-cutting concerns)
4. Pipe(管道)
- 作用:数据验证和转换
- 典型场景:DTO 验证、类型转换、数据清洗
- 设计理念:确保数据符合预期(像水管过滤器)
5. Controller & Service
- 作用:处理业务逻辑
- 设计理念:关注点分离
- Controller:处理 HTTP 请求(薄层)
- Service:处理业务逻辑(核心)
6. Exception Filter(异常过滤器)
- 作用:统一错误处理
- 典型场景:错误日志、统一错误响应格式
- 设计理念:集中式错误处理
二、核心概念深入理解 🎯
1. Controller(控制器):HTTP 层的守门员
核心职责:
- ✅ 接收 HTTP 请求
- ✅ 提取请求参数
- ✅ 调用 Service 处理业务逻辑
- ✅ 返回 HTTP 响应
最佳实践:
// ✅ 推荐:控制器保持简洁@Controller('user')export class UserController { constructor(private readonly userService: UserService) {}
@Post() async create(@Body() dto: CreateUserDto) { return this.userService.create(dto); }
@Get(':id') async findOne(@Param('id', ParseIntPipe) id: number) { return this.userService.findOne(id); }
@Put(':id') async update( @Param('id', ParseIntPipe) id: number, @Body() dto: UpdateUserDto, ) { return this.userService.update(id, dto); }
@Delete(':id') async remove(@Param('id', ParseIntPipe) id: number) { return this.userService.remove(id); }}
// ❌ 避免:在控制器中写业务逻辑@Controller('user')export class UserController { @Post() async create(@Body() dto: CreateUserDto) { // ❌ 不要在这里写复杂的业务逻辑 const user = await this.userModel.create(dto); await this.emailService.sendWelcome(user.email); await this.analyticsService.track('user_created'); await this.cacheService.invalidate('users'); return user; }}关键原则:
- 🎯 无状态:控制器不应该存储数据
- 🎯 薄层:只做参数提取和响应返回
- 🎯 委托:所有业务逻辑委托给 Service
💡 思考:为什么要这样设计?因为控制器是 HTTP 层的,如果业务逻辑写在这里,当你想提供 WebSocket 或 GraphQL 接口时,就得重复写一遍逻辑。
2. Service(服务):业务逻辑的核心
核心职责:
- ✅ 实现业务逻辑
- ✅ 数据库操作
- ✅ 调用外部服务
- ✅ 数据转换和处理
最佳实践:
// ✅ 推荐:Service 封装业务逻辑@Injectable()export class UserService { constructor( @InjectModel(User.name) private userModel: Model<User>, private readonly emailService: EmailService, private readonly cacheService: CacheService, ) {}
async create(dto: CreateUserDto): Promise<User> { // 业务逻辑:检查用户是否存在 const existing = await this.userModel.findOne({ email: dto.email }); if (existing) { throw new ConflictException('邮箱已被使用'); }
// 业务逻辑:创建用户 const user = await this.userModel.create(dto);
// 业务逻辑:发送欢迎邮件(异步,不阻塞) this.emailService.sendWelcome(user.email).catch(console.error);
// 业务逻辑:清除缓存 await this.cacheService.invalidate('users');
return user; }
async findOne(id: number): Promise<User> { // 业务逻辑:先查缓存 const cached = await this.cacheService.get(`user:${id}`); if (cached) return cached;
// 业务逻辑:查数据库 const user = await this.userModel.findById(id);
if (!user) { throw new NotFoundException(`用户 ${id} 不存在`); }
// 业务逻辑:写入缓存 await this.cacheService.set(`user:${id}`, user, 3600);
return user; }
async update(id: number, dto: UpdateUserDto): Promise<User> { const user = await this.findOne(id); // 复用查询逻辑
Object.assign(user, dto); await user.save();
// 清除缓存 await this.cacheService.invalidate(`user:${id}`);
return user; }}关键原则:
- 🎯 可注入:使用
@Injectable()装饰器 - 🎯 可测试:依赖注入使得单元测试更容易
- 🎯 单一职责:每个 Service 只负责一个业务领域
💡 进阶思考:当 Service 变得很大时,考虑拆分成多个 Service(如 UserQueryService、UserCommandService),这就是 CQRS 模式的思想。
3. Module(模块):代码组织的基石
核心职责:
- ✅ 组织相关代码
- ✅ 定义依赖关系
- ✅ 封装功能
- ✅ 控制可见性
最佳实践:
// ✅ 推荐:清晰的模块结构@Module({ imports: [ // 导入依赖的模块 MongooseModule.forFeature([ { name: User.name, schema: UserSchema } ]), EmailModule, // 邮件功能 CacheModule, // 缓存功能 ], controllers: [UserController], providers: [ UserService, // 可以注册多个 Provider { provide: 'USER_REPOSITORY', useClass: UserRepository, }, ], exports: [ UserService, // 导出给其他模块使用 ],})export class UserModule {}
// ✅ 推荐:根模块组织@Module({ imports: [ // 配置模块 ConfigModule.forRoot({ isGlobal: true, // 全局可用 envFilePath: '.env', }),
// 数据库模块 MongooseModule.forRoot(process.env.MONGODB_URI),
// 功能模块 UserModule, AuthModule, PostModule, ],})export class AppModule {}关键原则:
- 🎯 高内聚:相关功能放在一起
- 🎯 低耦合:模块间依赖清晰
- 🎯 封装性:只导出必要的服务
💡 模块化思维:把应用想象成乐高积木,每个模块是一块积木,可以独立开发、测试、替换。
4. Middleware(中间件):全局请求处理
核心职责:
- ✅ 日志记录
- ✅ 请求计时
- ✅ CORS 处理
- ✅ 请求体解析
最佳实践:
// ✅ 推荐:功能单一的中间件@Injectable()export class LoggerMiddleware implements NestMiddleware { private readonly logger = new Logger('HTTP');
use(req: Request, res: Response, next: NextFunction) { const { method, originalUrl, ip } = req; const userAgent = req.get('user-agent') || ''; const startTime = Date.now();
res.on('finish', () => { const { statusCode } = res; const duration = Date.now() - startTime;
const message = `${method} ${originalUrl} ${statusCode} - ${duration}ms - ${ip} - ${userAgent}`;
if (statusCode >= 500) { this.logger.error(message); } else if (statusCode >= 400) { this.logger.warn(message); } else { this.logger.log(message); } });
next(); }}
// 注册中间件export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('*'); // 应用到所有路由
// 或者只应用到特定路由 // .forRoutes({ path: 'user', method: RequestMethod.ALL }); }}关键原则:
- 🎯 全局性:适合处理全局关注点
- 🎯 顺序敏感:注意中间件的注册顺序
- 🎯 不要阻塞:避免耗时操作
5. Guard(守卫):安全的第一道防线
核心职责:
- ✅ 身份认证(Authentication)
- ✅ 权限授权(Authorization)
- ✅ 访问控制
最佳实践:
// ✅ 推荐:JWT 认证守卫@Injectable()export class JwtAuthGuard implements CanActivate { constructor( private jwtService: JwtService, private reflector: Reflector, ) {}
canActivate(context: ExecutionContext): boolean { // 检查是否标记为公开路由 const isPublic = this.reflector.get<boolean>( 'isPublic', context.getHandler(), ); if (isPublic) return true;
const request = context.switchToHttp().getRequest(); const token = this.extractTokenFromHeader(request);
if (!token) { throw new UnauthorizedException('未提供认证令牌'); }
try { const payload = this.jwtService.verify(token); request.user = payload; // 将用户信息附加到请求 return true; } catch { throw new UnauthorizedException('认证令牌无效'); } }
private extractTokenFromHeader(request: Request): string | undefined { const [type, token] = request.headers.authorization?.split(' ') ?? []; return type === 'Bearer' ? token : undefined; }}
// ✅ 推荐:角色守卫@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 request = context.switchToHttp().getRequest(); const user = request.user;
if (!user) { throw new UnauthorizedException('未认证'); }
const hasRole = requiredRoles.some(role => user.roles?.includes(role));
if (!hasRole) { throw new ForbiddenException('权限不足'); }
return true; }}
// 使用守卫@Controller('user')@UseGuards(JwtAuthGuard, RolesGuard) // 可以组合多个守卫export class UserController { @Get('profile') getProfile(@Request() req) { return req.user; // 访问守卫注入的用户信息 }
@Post('admin') @Roles('admin') // 需要 admin 角色 adminAction() { return 'Admin action'; }
@Get('public') @Public() // 公开路由,不需要认证 publicRoute() { return 'Public data'; }}
// 自定义装饰器export const Roles = (...roles: string[]) => SetMetadata('roles', roles);export const Public = () => SetMetadata('isPublic', true);关键原则:
- 🎯 决策者:决定请求是否继续
- 🎯 只读:可以读取请求,但不应修改响应
- 🎯 可组合:多个守卫可以链式使用
💡 安全提示:守卫的顺序很重要!先认证(JwtAuthGuard),再授权(RolesGuard)。
6. Interceptor(拦截器):强大的横切关注点
核心职责:
- ✅ 统一响应格式
- ✅ 日志记录
- ✅ 性能监控
- ✅ 缓存处理
- ✅ 错误转换
最佳实践:
// ✅ 推荐:统一响应格式拦截器@Injectable()export class ResponseInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next.handle().pipe( map(data => ({ code: 200, message: 'Success', data: data, timestamp: new Date().toISOString(), })), ); }}
// ✅ 推荐:性能监控拦截器@Injectable()export class PerformanceInterceptor implements NestInterceptor { private readonly logger = new Logger('Performance');
intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const request = context.switchToHttp().getRequest(); const { method, url } = request; const startTime = Date.now();
return next.handle().pipe( tap(() => { const duration = Date.now() - startTime;
if (duration > 1000) { this.logger.warn(`慢请求: ${method} ${url} - ${duration}ms`); } else { this.logger.log(`${method} ${url} - ${duration}ms`); } }), ); }}
// ✅ 推荐:缓存拦截器@Injectable()export class CacheInterceptor implements NestInterceptor { constructor(private cacheService: CacheService) {}
async intercept( context: ExecutionContext, next: CallHandler, ): Promise<Observable<any>> { const request = context.switchToHttp().getRequest(); const cacheKey = `cache:${request.url}`;
// 尝试从缓存获取 const cached = await this.cacheService.get(cacheKey); if (cached) { return of(cached); // 直接返回缓存 }
// 执行请求并缓存结果 return next.handle().pipe( tap(async (data) => { await this.cacheService.set(cacheKey, data, 300); // 缓存 5 分钟 }), ); }}
// 全局注册拦截器async function bootstrap() { const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors( new ResponseInterceptor(), new PerformanceInterceptor(), );
await app.listen(3000);}关键原则:
- 🎯 双向处理:可以在请求前后执行
- 🎯 RxJS 驱动:使用 Observable 处理异步流
- 🎯 可组合:多个拦截器可以链式执行
💡 RxJS 小贴士:
tap用于副作用(如日志),map用于转换数据,catchError用于错误处理。
7. Pipe(管道):数据验证的守护者
核心职责:
- ✅ 数据验证
- ✅ 类型转换
- ✅ 数据清洗
- ✅ 默认值设置
最佳实践:
// ✅ 推荐:使用 class-validator 进行验证import { IsEmail, IsString, MinLength, MaxLength, IsOptional } from 'class-validator';
export class CreateUserDto { @IsString({ message: '用户名必须是字符串' }) @MinLength(3, { message: '用户名至少 3 个字符' }) @MaxLength(20, { message: '用户名最多 20 个字符' }) username: string;
@IsEmail({}, { message: '邮箱格式不正确' }) email: string;
@IsString() @MinLength(6, { message: '密码至少 6 个字符' }) password: string;
@IsOptional() @IsString() bio?: string;}
// 在控制器中使用@Post()async create(@Body() dto: CreateUserDto) { // ValidationPipe 会自动验证 DTO return this.userService.create(dto);}
// ✅ 推荐:自定义管道进行类型转换@Injectable()export class ParseIntPipe implements PipeTransform<string, number> { transform(value: string, metadata: ArgumentMetadata): number { const val = parseInt(value, 10);
if (isNaN(val)) { throw new BadRequestException( `${metadata.data} 参数必须是数字,收到的是: ${value}` ); }
return val; }}
// ✅ 推荐:自定义验证管道@Injectable()export class FileValidationPipe implements PipeTransform { constructor(private readonly maxSize: number) {}
transform(value: any) { if (!value) { throw new BadRequestException('文件不能为空'); }
if (value.size > this.maxSize) { throw new BadRequestException( `文件大小不能超过 ${this.maxSize / 1024 / 1024}MB` ); }
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']; if (!allowedTypes.includes(value.mimetype)) { throw new BadRequestException('只支持 JPG、PNG、GIF 格式'); }
return value; }}
// 使用自定义管道@Post('upload')@UseInterceptors(FileInterceptor('file'))uploadFile( @UploadedFile(new FileValidationPipe(5 * 1024 * 1024)) file: Express.Multer.File) { return { filename: file.filename };}全局配置 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);}关键原则:
- 🎯 早期验证:在数据进入业务逻辑前验证
- 🎯 类型安全:确保数据类型正确
- 🎯 友好错误:提供清晰的错误信息
8. Exception Filter(异常过滤器):错误处理的最后防线
核心职责:
- ✅ 捕获异常
- ✅ 统一错误格式
- ✅ 错误日志记录
- ✅ 友好错误提示
最佳实践:
// ✅ 推荐:全局异常过滤器@Catch()export class AllExceptionsFilter implements ExceptionFilter { private readonly logger = new Logger(AllExceptionsFilter.name);
catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>();
let status = HttpStatus.INTERNAL_SERVER_ERROR; let message = '服务器内部错误'; let errors: any = null;
// 处理 HTTP 异常 if (exception instanceof HttpException) { status = exception.getStatus(); const exceptionResponse = exception.getResponse();
if (typeof exceptionResponse === 'string') { message = exceptionResponse; } else if (typeof exceptionResponse === 'object') { const responseObj = exceptionResponse as any; message = responseObj.message || '请求失败'; errors = responseObj.errors || null; } } // 处理 Mongoose 错误 else if (exception instanceof Error && exception.name === 'MongoError') { status = HttpStatus.BAD_REQUEST; message = '数据库操作失败';
// 处理唯一索引冲突 if ((exception as any).code === 11000) { message = '数据已存在'; } } // 处理其他异常 else if (exception instanceof Error) { message = exception.message || '服务器内部错误'; this.logger.error( `未处理的异常: ${exception.message}`, exception.stack, ); }
// 记录错误日志 this.logger.error( `${request.method} ${request.url} - ${status} - ${message}`, exception instanceof Error ? exception.stack : undefined, );
// 返回统一格式的错误响应 response.status(status).json({ code: status, message: Array.isArray(message) ? message[0] : message, data: null, timestamp: new Date().toISOString(), path: request.url, ...(errors && { errors }), }); }}
// 全局注册async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalFilters(new AllExceptionsFilter()); await app.listen(3000);}关键原则:
- 🎯 集中处理:统一的错误处理逻辑
- 🎯 详细日志:记录完整的错误信息
- 🎯 友好提示:向用户返回易于理解的错误信息
- 🎯 安全性:不要暴露敏感信息(如堆栈跟踪)
三、完整请求流程演示 🔄
让我们通过一个完整的例子,展示请求从进入到返回的全过程。
场景:获取用户信息
用户发送请求:
GET /user/123Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...步骤 1:Middleware(中间件)
// LoggerMiddleware 记录请求[2024-01-15 10:30:00] GET /user/123 - 127.0.0.1步骤 2:Guard(守卫)
// JwtAuthGuard 验证 token✅ Token 验证通过request.user = { id: 456, username: 'alice', roles: ['user'] }步骤 3:Interceptor(请求前)
// PerformanceInterceptor 记录开始时间startTime = 1705294200000步骤 4:Pipe(管道)
// ParseIntPipe 将字符串 '123' 转换为数字 123id: 123 (number)步骤 5:Controller(控制器)
@Get(':id')async findOne(@Param('id', ParseIntPipe) id: number) { return this.userService.findOne(id);}步骤 6:Service(服务)
async findOne(id: number): Promise<User> { const user = await this.userModel.findById(id);
if (!user) { throw new NotFoundException(`用户 ${id} 不存在`); }
return user;}步骤 7:Interceptor(响应后)
// PerformanceInterceptor 记录响应时间duration = 15msconsole.log(`GET /user/123 - 15ms`)
// ResponseInterceptor 统一响应格式{ code: 200, message: 'Success', data: { id: 123, username: 'alice', email: 'alice@example.com' }, timestamp: '2024-01-15T10:30:00.123Z'}步骤 8:返回响应
HTTP/1.1 200 OKContent-Type: application/json
{ "code": 200, "message": "Success", "data": { "id": 123, "username": "alice", "email": "alice@example.com" }, "timestamp": "2024-01-15T10:30:00.123Z"}💡 流程总结:每一层都有明确的职责,互不干扰,这就是”关注点分离”的威力!
四、项目结构最佳实践 📁
标准项目结构
src/├── common/ # 公共模块│ ├── dto/ # 公共 DTO│ ├── filters/ # 异常过滤器│ │ └── all-exceptions.filter.ts│ ├── interceptors/ # 拦截器│ │ ├── response.interceptor.ts│ │ └── performance.interceptor.ts│ ├── guards/ # 守卫│ │ ├── jwt-auth.guard.ts│ │ └── roles.guard.ts│ ├── pipes/ # 管道│ │ └── validation.pipe.ts│ ├── decorators/ # 自定义装饰器│ │ ├── roles.decorator.ts│ │ └── public.decorator.ts│ └── utils/ # 工具函数│ └── helpers.ts├── config/ # 配置文件│ ├── database.config.ts│ ├── jwt.config.ts│ └── app.config.ts├── user/ # 用户模块│ ├── dto/│ │ ├── create-user.dto.ts│ │ └── update-user.dto.ts│ ├── schemas/│ │ └── user.schema.ts│ ├── user.controller.ts│ ├── user.service.ts│ ├── user.module.ts│ └── user.controller.spec.ts├── auth/ # 认证模块│ ├── strategies/│ │ └── jwt.strategy.ts│ ├── auth.controller.ts│ ├── auth.service.ts│ └── auth.module.ts├── app.module.ts # 根模块└── main.ts # 应用入口模块组织原则
1. 按功能组织
- ✅ 每个功能一个模块(用户、认证、支付等)
- ✅ 相关代码集中在一起
- ✅ 便于维护和扩展
2. 高内聚低耦合
- ✅ 模块内部高度相关
- ✅ 模块之间依赖清晰
- ✅ 通过接口而非实现依赖
3. 单一职责
- ✅ 每个模块只负责一个功能领域
- ✅ 避免模块过大
- ✅ 便于理解和测试
文件命名规范
| 文件类型 | 命名规范 | 示例 |
|---|---|---|
| 控制器 | *.controller.ts | user.controller.ts |
| 服务 | *.service.ts | user.service.ts |
| 模块 | *.module.ts | user.module.ts |
| DTO | *.dto.ts | create-user.dto.ts |
| Schema | *.schema.ts | user.schema.ts |
| 守卫 | *.guard.ts | jwt-auth.guard.ts |
| 拦截器 | *.interceptor.ts | response.interceptor.ts |
| 过滤器 | *.filter.ts | http-exception.filter.ts |
| 管道 | *.pipe.ts | validation.pipe.ts |
| 测试文件 | *.spec.ts | user.service.spec.ts |
五、设计原则与代码质量 🎨
1. 依赖注入原则(DI)
核心理念:不要在类内创建依赖,让框架注入给你。
// ❌ 错误:手动创建依赖export class UserService { private userModel = new UserModel(); private emailService = new EmailService();
// 问题: // 1. 难以测试(无法 mock) // 2. 难以替换实现 // 3. 紧耦合}
// ✅ 正确:依赖注入@Injectable()export class UserService { constructor( @InjectModel(User.name) private userModel: Model<User>, private readonly emailService: EmailService, ) {}
// 优势: // 1. 易于测试(可以 mock) // 2. 易于替换实现 // 3. 松耦合}优势:
- 🎯 可测试性:易于 mock 依赖
- 🎯 灵活性:易于替换实现
- 🎯 解耦:降低类之间的耦合度
2. 单一职责原则(SRP)
核心理念:每个类只有一个改变的理由。
// ✅ 正确:职责分离
// Controller 只负责 HTTP 请求处理@Controller('user')export class UserController { constructor(private readonly userService: UserService) {}
@Post() create(@Body() dto: CreateUserDto) { return this.userService.create(dto); }}
// Service 只负责业务逻辑@Injectable()export class UserService { constructor( private readonly userRepository: UserRepository, private readonly emailService: EmailService, ) {}
async create(dto: CreateUserDto) { const user = await this.userRepository.create(dto); await this.emailService.sendWelcome(user.email); return user; }}
// Repository 只负责数据访问@Injectable()export class UserRepository { constructor( @InjectModel(User.name) private userModel: Model<User>, ) {}
async create(dto: CreateUserDto) { return this.userModel.create(dto); }
async findById(id: string) { return this.userModel.findById(id); }}💡 思考:当你需要修改数据库查询逻辑时,只需改 Repository;当你需要修改业务逻辑时,只需改 Service。这就是单一职责的威力!
3. DRY 原则(Don’t Repeat Yourself)
核心理念:重复的代码应该提取出来。
// ❌ 错误:重复的验证逻辑@Post('user')createUser(@Body() dto: any) { if (!dto.email || !dto.password) { throw new BadRequestException('参数错误'); } // ...}
@Post('admin')createAdmin(@Body() dto: any) { if (!dto.email || !dto.password) { throw new BadRequestException('参数错误'); } // ...}
// ✅ 正确:使用 DTO 和 ValidationPipeexport class CreateUserDto { @IsEmail() email: string;
@IsString() @MinLength(6) password: string;}
@Post('user')createUser(@Body() dto: CreateUserDto) { // ValidationPipe 自动验证 return this.userService.create(dto);}
@Post('admin')createAdmin(@Body() dto: CreateUserDto) { // 复用相同的 DTO return this.adminService.create(dto);}六、性能优化策略 ⚡
1. 数据库优化
// ✅ 使用索引@Schema()export class User { @Prop({ index: true }) email: string;
@Prop({ index: true }) username: string;}
// ✅ 避免 N+1 查询// ❌ 错误const users = await this.userModel.find();for (const user of users) { const posts = await this.postModel.find({ userId: user.id });}
// ✅ 正确:使用 populateconst users = await this.userModel.find().populate('posts');2. 缓存策略
@Injectable()export class UserService { constructor( @InjectModel(User.name) private userModel: Model<User>, @Inject('REDIS_CLIENT') private redis: Redis, ) {}
async findOne(id: string): Promise<User> { // 尝试从 Redis 获取 const cached = await this.redis.get(`user:${id}`); if (cached) { return JSON.parse(cached); }
// 查询数据库 const user = await this.userModel.findById(id);
// 写入 Redis(1 小时过期) await this.redis.setex(`user:${id}`, 3600, JSON.stringify(user));
return user; }}3. 异步优化
// ❌ 串行:总时间 = 时间1 + 时间2 + 时间3const user = await this.userService.findOne(id);const posts = await this.postService.findByUser(id);const comments = await this.commentService.findByUser(id);
// ✅ 并行:总时间 = max(时间1, 时间2, 时间3)const [user, posts, comments] = await Promise.all([ this.userService.findOne(id), this.postService.findByUser(id), this.commentService.findByUser(id),]);七、安全最佳实践 🔒
1. 输入验证
export class CreateUserDto { @IsEmail({}, { message: '邮箱格式不正确' }) email: string;
@IsString() @MinLength(6, { message: '密码至少 6 个字符' }) @MaxLength(20, { message: '密码最多 20 个字符' }) @Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, { message: '密码必须包含大小写字母和数字', }) password: string;}2. 保护敏感信息
// ✅ 方法 1:使用 select 排除字段const user = await this.userModel .findById(id) .select('-password -__v') .exec();
// ✅ 方法 2:在 Schema 中设置默认不返回@Prop({ select: false })password: string;3. 限流保护
import rateLimit from 'express-rate-limit';
app.use( rateLimit({ windowMs: 15 * 60 * 1000, // 15 分钟 max: 100, // 最多 100 个请求 message: '请求过于频繁,请稍后再试', }),);八、总结与检查清单 ✅
核心知识点回顾
架构层次:
- ✅ 理解了请求处理的完整流程
- ✅ 掌握了每个层次的职责和作用
- ✅ 理解了为什么需要这些层次
核心概念:
- ✅ Controller:HTTP 请求处理
- ✅ Service:业务逻辑封装
- ✅ Module:代码组织单元
- ✅ Middleware:全局请求预处理
- ✅ Guard:认证和授权
- ✅ Interceptor:横切关注点
- ✅ Pipe:数据验证和转换
- ✅ Exception Filter:统一错误处理
设计原则:
- ✅ 依赖注入(DI)
- ✅ 单一职责(SRP)
- ✅ DRY 原则
- ✅ 关注点分离
项目检查清单
代码组织:
- 项目结构清晰,按功能模块组织
- 文件命名符合规范
- 模块职责单一,依赖关系清晰
代码质量:
- 使用 TypeScript 类型系统
- 遵循 ESLint 规则
- 使用 Prettier 格式化代码
- 编写单元测试和 E2E 测试
安全性:
- 所有输入都经过验证
- 敏感信息不会暴露
- 实现了认证和授权
- 配置了限流保护
性能:
- 数据库查询优化(索引、避免 N+1)
- 实现了缓存策略
- 使用分页处理大量数据
- 异步操作并行执行
参考资源 📚
官方文档
设计模式与架构
社区资源
结语 🎉
恭喜你完成了 NestJS 架构设计的学习!🎊
通过这篇文章,我们系统地梳理了 NestJS 的整体架构,深入理解了每个核心概念,掌握了最佳实践。这些知识将为你构建企业级应用打下坚实的基础。
记住核心原则:
- 🎯 关注点分离:每个层次各司其职
- 🎯 依赖注入:让代码更灵活、可测试
- 🎯 类型安全:充分利用 TypeScript
- 🎯 最佳实践:遵循社区规范
NestJS 是一个强大而优雅的框架,它的设计理念值得我们深入学习和实践。希望这篇文章能帮助你建立完整的知识体系,在实际项目中游刃有余。
持续学习,不断精进! 💪
如果在学习过程中遇到任何问题,欢迎交流讨论 (。・ω・。)ノ♡
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!