深入理解 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:
// main.ts
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/123
Authorization: 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' 转换为数字 123
id: 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 = 15ms
console.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 OK
Content-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 和 ValidationPipe
export 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 });
}
// ✅ 正确:使用 populate
const 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 + 时间3
const 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 是一个强大而优雅的框架,它的设计理念值得我们深入学习和实践。希望这篇文章能帮助你建立完整的知识体系,在实际项目中游刃有余。
持续学习,不断精进! 💪
如果在学习过程中遇到任何问题,欢迎交流讨论 (。・ω・。)ノ♡
- 本文链接:https://fridolph.top/posts/2026-01-20__nest15-all
- 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 许可协议。