Skip to content

异常过滤器

一、NestJS 异常处理机制

NestJS 有一个内置的全局异常层:任何未被处理的异常都会被捕获,并自动返回合适的 HTTP 响应。

默认行为:

  • HttpException 及其子类 → 自动提取状态码和消息
  • 其他异常(Error、数据库错误等)→ 返回 500 Internal Server Error

异常过滤器(ExceptionFilter)允许你完全接管这个处理过程。


二、内置 HTTP 异常类

NestJS 内置了完整的 HTTP 异常类层次:

typescript
// 基类
throw new HttpException('自定义消息', HttpStatus.BAD_REQUEST);
throw new HttpException({ code: 'USER_NOT_FOUND', message: '用户不存在' }, 404);

// 常用子类(自动设置状态码)
throw new BadRequestException('参数错误');           // 400
throw new UnauthorizedException('未登录或 Token 过期'); // 401
throw new ForbiddenException('没有权限');             // 403
throw new NotFoundException(`用户 #${id} 不存在`);    // 404
throw new MethodNotAllowedException();               // 405
throw new NotAcceptableException();                  // 406
throw new RequestTimeoutException('请求超时');        // 408
throw new ConflictException('邮箱已被注册');           // 409
throw new GoneException();                           // 410
throw new PayloadTooLargeException();                // 413
throw new UnsupportedMediaTypeException();           // 415
throw new UnprocessableEntityException();            // 422
throw new TooManyRequestsException('请求过于频繁');    // 429
throw new InternalServerErrorException('服务器错误'); // 500
throw new NotImplementedException();                 // 501
throw new BadGatewayException();                     // 502
throw new ServiceUnavailableException();             // 503
throw new GatewayTimeoutException();                 // 504

传递结构化消息

typescript
// 传入对象时,整个对象作为 response body
throw new BadRequestException({
  code: 'VALIDATION_FAILED',
  message: '参数验证失败',
  details: [
    { field: 'email', error: '邮箱格式不正确' },
    { field: 'age', error: '年龄必须大于 0' },
  ],
});

三、自定义异常类

推荐: 在业务层抛出语义化的业务异常,而非直接抛出 HTTP 异常。这样 Service 层不依赖 HTTP 语义,更便于测试和复用。

typescript
// exceptions/business.exception.ts
export class BusinessException extends HttpException {
  constructor(
    private readonly code: string,
    message: string,
    status: number = HttpStatus.BAD_REQUEST,
  ) {
    super({ code, message }, status);
  }

  getCode() { return this.code; }
}

// 具体业务异常
export class UserNotFoundException extends BusinessException {
  constructor(id: number) {
    super('USER_NOT_FOUND', `用户 #${id} 不存在`, HttpStatus.NOT_FOUND);
  }
}

export class EmailAlreadyExistsException extends BusinessException {
  constructor(email: string) {
    super('EMAIL_EXISTS', `邮箱 ${email} 已被注册`, HttpStatus.CONFLICT);
  }
}

// 在 Service 中抛出
async findOne(id: number): Promise<User> {
  const user = await this.userRepo.findOne({ where: { id } });
  if (!user) throw new UserNotFoundException(id);
  return user;
}

四、自定义异常过滤器

捕获特定异常

typescript
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();
    const exceptionResponse = exception.getResponse();

    // 提取消息(可能是字符串或对象)
    const message =
      typeof exceptionResponse === 'string'
        ? exceptionResponse
        : (exceptionResponse as any).message;

    response.status(status).json({
      code: status,
      message,
      path: request.url,
      method: request.method,
      timestamp: new Date().toISOString(),
    });
  }
}

捕获所有异常(生产推荐)

typescript
@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 code = 'INTERNAL_ERROR';

    if (exception instanceof HttpException) {
      status = exception.getStatus();
      const exceptionResponse = exception.getResponse();
      message =
        typeof exceptionResponse === 'string'
          ? exceptionResponse
          : (exceptionResponse as any).message ?? message;
      code = (exceptionResponse as any).code ?? `HTTP_${status}`;
    }

    // 生产环境:记录非 4xx 错误(服务器端问题)
    if (status >= 500) {
      this.logger.error(
        `${request.method} ${request.url} → ${status}`,
        exception instanceof Error ? exception.stack : String(exception),
      );
    }

    response.status(status).json({
      code,
      message,
      path: request.url,
      timestamp: new Date().toISOString(),
      // 开发环境可附加堆栈
      ...(process.env.NODE_ENV === 'development' && exception instanceof Error
        ? { stack: exception.stack }
        : {}),
    });
  }
}

五、ArgumentsHost:多传输层支持

ArgumentsHost 是请求上下文的抽象,支持 HTTP、WebSocket、RPC:

typescript
@Catch()
export class UniversalExceptionFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const type = host.getType<'http' | 'ws' | 'rpc'>();

    if (type === 'http') {
      const ctx = host.switchToHttp();
      const response = ctx.getResponse<Response>();
      // HTTP 处理
      response.status(500).json({ message: 'Error' });

    } else if (type === 'ws') {
      const ctx = host.switchToWs();
      const client = ctx.getClient<WebSocket>();
      // WebSocket 处理
      client.send(JSON.stringify({ event: 'error', message: 'Error' }));

    } else if (type === 'rpc') {
      const ctx = host.switchToRpc();
      // gRPC/Microservice 处理
      throw new RpcException({ message: 'Error', statusCode: 500 });
    }
  }
}

六、过滤器的绑定层级

typescript
// 1. 路由级别(优先级最高)
@Get(':id')
@UseFilters(UserNotFoundFilter)
findOne(@Param('id') id: string) {}

// 2. 控制器级别
@UseFilters(HttpExceptionFilter)
@Controller('users')
export class UsersController {}

// 3. 全局(推荐方式:通过 APP_FILTER 可以注入依赖)
@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: AllExceptionsFilter,
    },
  ],
})
export class AppModule {}

// 也可以在 main.ts 注册(但无法注入 NestJS Provider)
app.useGlobalFilters(new AllExceptionsFilter());

多过滤器时的捕获顺序:

typescript
@UseFilters(FilterA, FilterB)
// 若 FilterA 的 @Catch(SomeError) 匹配,FilterA 处理
// 若 FilterA 不匹配,FilterB 尝试
// 全局过滤器最后兜底

七、与 ValidationPipe 的配合

ValidationPipe 验证失败时抛出 BadRequestException,包含详细字段错误:

typescript
// ValidationPipe 默认抛出:
// BadRequestException {
//   message: ['name must be longer than or equal to 2 characters', 'email must be an email'],
//   error: 'Bad Request',
//   statusCode: 400
// }

// 在异常过滤器中处理(提取字段级错误):
@Catch(BadRequestException)
export class ValidationExceptionFilter implements ExceptionFilter {
  catch(exception: BadRequestException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const exceptionResponse = exception.getResponse() as any;

    response.status(400).json({
      code: 'VALIDATION_FAILED',
      message: '请求参数验证失败',
      errors: Array.isArray(exceptionResponse.message)
        ? exceptionResponse.message
        : [exceptionResponse.message],
    });
  }
}

八、异常过滤器 vs 拦截器:如何选择?

场景推荐
统一格式化错误响应(HTTP 状态码、message)ExceptionFilter
记录错误日志(异常发生时)ExceptionFilter
将某类错误转换为另一类(数据库约束 → 业务异常)Interceptor(catchError
将响应体从对象包装为 {code, data} 格式Interceptor(map

最佳实践:

  • Interceptor 的 catchError 做异常转换(第三方库错误 → NestJS HttpException)
  • ExceptionFilter 做异常呈现(格式化为统一 JSON 响应)

可运行 Demo: practice/02-request-lifecycle — 全局异常过滤器统一响应格式


常见错误

错误原因解决
自定义异常过滤器不生效未注册或注册顺序错误@UseFilters() 装饰器或 app.useGlobalFilters() 全局注册
HttpException 子类被过滤器捕获不到泛型类型不匹配@Catch(HttpException)@Catch() 捕获所有
生产环境暴露堆栈信息过滤器直接透传 error.stackNODE_ENV !== 'production' 时才包含 stack

NestJS 深度学习体系