FORMA

异常处理

异常过滤器统一错误响应格式。见 pipes 抛出的 BadRequestException 等。

NestJS 提供了强大的异常处理层。应用在运行期间抛出的异常(无论是主动抛出还是意外错误)都会被统一的异常层捕获,并转换成标准的 HTTP 响应。开发者可以通过内置异常类、自定义异常以及异常过滤器来定制错误响应逻辑。

一、内置异常类

NestJS 在 @nestjs/common 中提供了一系列继承自 HttpException 的内置异常类,覆盖了常见的 HTTP 错误状态码。使用这些异常可以使代码更具可读性。

常用内置异常类

异常类HTTP 状态码描述
BadRequestException400请求参数错误(如验证失败)
UnauthorizedException401未认证(缺少有效凭证)
ForbiddenException403无权限访问资源
NotFoundException404资源未找到
ConflictException409请求与当前资源状态冲突
InternalServerErrorException500服务器内部错误(默认)
NotAcceptableException406无法根据请求 Accept 头生成响应
RequestTimeoutException408请求超时
MethodNotAllowedException405HTTP 方法不允许
UnsupportedMediaTypeException415不支持的媒体类型

注意:@nestjs/common 没有内置 TooManyRequestsException。429(请求过于频繁)需用 new HttpException('Too Many Requests', HttpStatus.TOO_MANY_REQUESTS) 自行抛出,或直接使用 @nestjs/throttler 提供的限流守卫(其内部抛出 ThrottlerException)。

使用示例

ts
@Get(':id')
async findOne(@Param('id') id: number) {
  const user = await this.usersService.findOne(id);
  if (!user) {
    throw new NotFoundException(`User with id ${id} not found`);
  }
  return user;
}

所有内置异常继承自 HttpException

HttpException 是所有异常类的基类。你也可以直接使用它,传入状态码和消息:

ts
throw new HttpException("自定义错误", HttpStatus.FORBIDDEN);

HttpStatus 枚举可从 @nestjs/common 导入。

二、自定义异常

如果内置异常不满足需求,可以创建自己的异常类。通常有两种方式:

1. 直接扩展 HttpException

ts
import { HttpException, HttpStatus } from "@nestjs/common";

export class BusinessException extends HttpException {
  constructor(message: string, status: HttpStatus = HttpStatus.BAD_REQUEST) {
    super(message, status);
  }
}

2. 扩展内置异常类

ts
import { BadRequestException } from "@nestjs/common";

export class ValidationFailedException extends BadRequestException {
  constructor(errorDetails: any) {
    super({
      message: "Validation failed",
      errors: errorDetails,
    });
  }
}

自定义响应体HttpException 构造函数可以接收对象作为第一个参数,从而自定义返回的 JSON 结构。

ts
throw new HttpException(
  {
    status: HttpStatus.FORBIDDEN,
    error: "custom error message",
    timestamp: new Date().toISOString(),
  },
  HttpStatus.FORBIDDEN,
);

三、异常过滤器(Exception Filters)

虽然内置异常层可以自动处理并返回标准错误格式,但有时需要完全控制错误响应(如记录日志、修改结构、集成第三方错误追踪系统)。这时可以使用异常过滤器

1. 实现 ExceptionFilter 接口

每个过滤器必须实现 catch(exception: T, host: ArgumentsHost) 方法。ArgumentsHost 提供了访问底层请求/响应对象的能力。

ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from "@nestjs/common";
import { Request, Response } from "express";

@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 message = exception.getResponse() || exception.message;

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message,
    });
  }
}
  • @Catch() 可以传多个异常类型,也可以不传(表示捕获所有异常)。
  • exception.getResponse() 获取响应体(字符串或对象),getStatus() 获取状态码。

2. 过滤器作用域

异常过滤器可以有三种作用域,灵活程度递增:

作用域装饰器 / 方法影响范围
方法级@UseFilters(MyFilter) 在方法上仅该方法
控制器级@UseFilters(MyFilter) 在控制器类上该控制器所有方法
全局级app.useGlobalFilters(new MyFilter())整个应用

方法/控制器级示例

ts
@Controller("users")
@UseFilters(HttpExceptionFilter) // 控制器级
export class UsersController {
  @Get(":id")
  @UseFilters(AnotherFilter) // 方法级(覆盖控制器级?实际会组合,多个过滤器顺序执行)
  findOne() {}
}

全局过滤器注册

ts
// main.ts
import { HttpExceptionFilter } from "./filters/http-exception.filter";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}

如果需要在全局过滤器中注入依赖(如日志服务),则不建议使用 useGlobalFilters,而是通过在模块中提供过滤器(使用 APP_FILTER 令牌),让框架进行依赖注入。

ts
import { Module } from "@nestjs/common";
import { APP_FILTER } from "@nestjs/core";
import { HttpExceptionFilter } from "./filters/http-exception.filter";

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}

这种方式注册的全局过滤器可以访问模块中的任何提供者。

3. 捕获所有异常

如果希望过滤器处理所有未捕获的异常(不仅 HttpException),可以省略 @Catch() 中的参数:

ts
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    // 处理日志、统一兜底响应
  }
}

四、内置全局异常过滤器

NestJS 内置了一个全局异常过滤器,当请求到达异常处理层而未注册任何自定义过滤器时,该内置过滤器会接管。

  • 默认行为
    • 如果异常是 HttpException 或其子类,则返回对应的 statusCodemessage
    • 如果是其他错误(如 TypeError, Error),则返回 500 Internal Server Error,并隐藏内部错误细节(在生产环境)。
  • 结构:默认返回格式为 { statusCode: number, message: string, error?: string }

你不需要主动引入内置过滤器,它是框架的一部分。但在需要统一错误结构时,通常推荐自定义全局过滤器。

五、最佳实践

  1. 优先使用内置异常类:它们已经涵盖了绝大部分业务场景,使用它们可使代码意图清晰。
  2. 自定义业务异常:对于特定业务规则错误,扩展 HttpException 并携带错误码或附加信息。
  3. 全局异常过滤器:用于统一响应格式、记录错误日志、接入监控系统(Sentry/DataDog)。
  4. 区分可恢复错误与崩溃:不要捕获不该捕获的低级异常(如 Error),让它们触发全局过滤器并记录堆栈。
  5. 使用 class-validator 管道验证:验证失败自动抛出 BadRequestException,无需手动处理。

总结

概念作用
HttpException 基类所有异常的根,可设置状态码和消息
内置异常类快速生成常见 HTTP 错误响应
自定义异常封装特定业务错误或附加信息
@Catch() 过滤器捕获指定异常,自定义响应逻辑
@UseFilters()方法/控制器级应用过滤器
app.useGlobalFilters / APP_FILTER全局过滤器,统一错误处理
内置全局过滤器默认的兜底异常处理,生产环境不暴露内部错误

通过合理使用异常过滤器,你可以实现对错误响应的完全控制,同时保持代码整洁。

参考文献

以下链接在编写时均可正常访问:

资料说明
NestJS 文档官方
Exception filters本章主题
Request lifecycle执行顺序