NestJS最佳实践:如何避免在服务层处理HTTP特定错误?
NestJS 服务层与协议解耦的错误处理最佳实践
你的思路完全正确——服务层不应该耦合HTTP这类传输协议的细节,否则后续适配GRPC、MQTT等其他协议时,需要大量修改业务代码。下面是几种成熟的NestJS最佳实践方案:
1. 自定义业务异常 + 全局异常过滤器(推荐)
服务层抛出语义化的业务异常,由全局过滤器统一将异常转换为对应协议的错误响应,彻底实现业务逻辑与协议解耦。
步骤1:定义业务异常类
// src/common/exceptions/user-not-found.exception.ts export class UserNotFoundException extends Error { constructor(public readonly userId: number) { super(`User with ID ${userId} not found`); this.name = 'UserNotFoundException'; } }
步骤2:服务层抛出业务异常
import { Injectable } from '@nestjs/common'; import { UserNotFoundException } from '../common/exceptions/user-not-found.exception'; @Injectable() export class UserService { async remove(id: number): Promise<Partial<User>> { const user = await this.userRepository.findOne({ where: { id } }); if (!user) { // 抛出业务异常,而非HTTP相关错误 throw new UserNotFoundException(id); } await this.userRepository.remove(user); return { id }; } }
步骤3:全局异常过滤器处理异常
// src/common/filters/business-exception.filter.ts import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus } from '@nestjs/common'; import { Response } from 'express'; import { UserNotFoundException } from '../exceptions/user-not-found.exception'; @Catch(UserNotFoundException) export class BusinessExceptionFilter implements ExceptionFilter { catch(exception: UserNotFoundException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); response.status(HttpStatus.NOT_FOUND).json({ statusCode: HttpStatus.NOT_FOUND, message: exception.message, error: 'Not Found' }); } }
步骤4:注册全局过滤器
在main.ts中注册过滤器,全局生效:
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { BusinessExceptionFilter } from './common/filters/business-exception.filter'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalFilters(new BusinessExceptionFilter()); await app.listen(3000); } bootstrap();
这种方案的优势:
- 服务层只关注业务逻辑,异常语义清晰
- 不同协议适配器(HTTP控制器、GRPC拦截器等)可各自处理异常,适配灵活
- 符合NestJS的AOP(面向切面)设计理念
2. 通用Result类型封装返回值
如果你不习惯用异常处理业务错误,可以定义通用的Result类型,明确区分成功与失败状态:
定义Result类型
// src/common/types/result.type.ts type SuccessResult<T> = { success: true; data: T; }; type ErrorResult = { success: false; errorCode: number; errorMessage: string; }; export type Result<T> = SuccessResult<T> | ErrorResult;
服务层返回Result对象
async remove(id: number): Promise<Result<Partial<User>>> { const user = await this.userRepository.findOne({ where: { id } }); if (!user) { return { success: false, errorCode: 404, errorMessage: 'user not found' }; } await this.userRepository.remove(user); return { success: true, data: { id } }; }
控制器处理Result
// user.controller.ts import { Controller, Delete, HttpException, Param } from '@nestjs/common'; import { UserService } from './user.service'; import { Result } from '../common/types/result.type'; @Controller('users') export class UserController { constructor(private userService: UserService) {} @Delete(':id') async remove(@Param('id') id: number) { const result = await this.userService.remove(id); if (!result.success) { throw new HttpException(result.errorMessage, result.errorCode); } return result.data; } }
这种方案更偏向“错误优先”的返回风格,适合对异常使用较为谨慎的场景。
3. 通用业务异常 + 统一映射
如果需要处理多种业务错误,可以定义通用的业务异常类,通过错误码映射到不同的HTTP状态:
定义通用业务异常
// src/common/exceptions/business-error.exception.ts export class BusinessErrorException extends Error { constructor(public readonly errorCode: number, message: string) { super(message); this.name = 'BusinessErrorException'; } }
服务层抛出通用异常
if (!user) { throw new BusinessErrorException(404, 'user not found'); }
全局过滤器统一映射
// src/common/filters/business-error.filter.ts import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common'; import { Response } from 'express'; import { BusinessErrorException } from '../exceptions/business-error.exception'; @Catch(BusinessErrorException) export class BusinessErrorFilter implements ExceptionFilter { catch(exception: BusinessErrorException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); response.status(exception.errorCode).json({ statusCode: exception.errorCode, message: exception.message }); } }
核心原则
无论选择哪种方案,核心都是:服务层专注业务逻辑实现,协议相关的错误处理交给适配器层(控制器、拦截器、过滤器),确保业务代码的可复用性和扩展性。
内容的提问来源于stack exchange,提问作者Christian LSANGOLA
相关产品推荐
相关产品推荐

