Skip to content

NestJS 异常过滤器

目录

异常过滤器

Nest 内置了一个异常处理层,负责捕获应用中所有未被显式处理的异常。当你的代码中出现异常却没有被 try-catch 捕获时,异常过滤器会自动接手,保证程序不会崩溃,同时给前端返回一个格式统一且易理解的错误信息。

默认情况下,Nest 使用一个全局异常过滤器,专门处理 HttpException 及其派生类(例如 404 找不到资源、401 未授权等),它会将这些异常转换成标准的 HTTP 响应。对于那些不属于 HttpException 的错误(比如运行时错误、数据库连接异常等):

ts
throw new Error('This is a test error');

这个过滤器会返回一个通用的 500 状态码和“Internal server error”的信息,避免把内部细节暴露给客户端,提升安全性。

json
{
  "statusCode": 500,
  "message": "Internal server error"
}

此外,Nest 对第三方库 http-errors 有一定的兼容支持。如果你抛出的异常对象里带有 statusCodemessage 这两个属性,Nest 也会识别它们,并把它们原样返回,而不会一律替换成默认的 500 错误,这让你在使用其他库时也能保持一致的错误处理体验。

ts
throw { statusCode: 400, message: 'This is a test error' };

抛出标准异常

Nest 提供了一个内置的异常类 HttpException,它在 @nestjs/common 包中导出。对于基于 HTTP 的 REST 或 GraphQL API 应用,推荐的做法是在出现错误时,通过抛出标准的 HTTP 异常来统一响应错误。

举个例子,假设在 CatsController 中有一个处理 GET 请求的 findAll() 方法。如果这个方法出现错误,我们可以直接抛出一个 HttpException 来表示访问被禁止:

ts
@Get()
async findAll() {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}

这里的 HttpStatus 是从 @nestjs/common 导入的一个枚举,包含了 HTTP 的各种状态码。

当客户端调用这个接口时,服务器会返回如下 JSON 响应:

json
{
  "statusCode": 403,
  "message": "Forbidden"
}

HttpException 构造函数详解

  • 第一个参数:response 定义返回给客户端的 JSON 响应体。它既可以是字符串,也可以是对象。
  • 第二个参数:status HTTP 状态码,必须是合法的数字。建议使用官方的 HttpStatus 枚举(从 @nestjs/common 导入),保证语义清晰且减少魔法数字。

返回内容格式

默认情况下,Nest 会自动包装一个对象,包含两个字段:

  • statusCode:对应你传入的 HTTP 状态码
  • message:对应该状态码的简短描述(例如 403 对应 “Forbidden”)

如果你只传入字符串作为 response 参数,Nest 会用它覆盖 message,如果传入对象,则会直接序列化这个对象作为完整的响应体。

HttpException 构造函数还支持第三个可选参数 options,它允许你传递一个 cause,通常用来保存底层异常信息,它不会被序列化到 HTTP 响应里,但可以用来做日志记录或调试,主要用于内部错误追踪。

ts
@Get()
async findAll() {
  try {
    await this.service.findAll();
  } catch (error) {
    throw new HttpException({
      status: HttpStatus.FORBIDDEN,
      error: 'This is a custom message',
    }, HttpStatus.FORBIDDEN, {
      cause: error,       // new Error('This is a test error')
    });
  }
}

此时返回给客户端的 JSON 是:

json
{
  "status": 403,
  "error": "This is a custom message"
}

cause 仍可在服务器端访问,方便你将详细异常写入日志或做后续处理。

options 还支持一个 description 字段,它是一个字符串,用于描述异常的详细信息,它也会被序列化到 HTTP 响应里,通常用于自定义的错误描述信息,可以在你自定义异常过滤器或日志处理逻辑里读取,用来提供更详细的上下文。

HttpException 构造函数的TypeScript类型定义如下:

ts
class HttpException extends Error {
  constructor(
    response: string | Record<string, any>,
    status: number,
    options?: {
      cause?: unknown;
      description?: string;
    }
  );
}

异常日志记录

默认的异常过滤器对内置的异常(如 HttpException 及其子类)是不会进行日志打印的。这是因为这些异常被认为是应用正常流程的一部分,不应当每次都触发错误日志,避免控制台被大量“噪音”淹没。

同样,其他内置异常类型,如 WsException(WebSocket 异常)和 RpcException(远程过程调用异常)也遵循相同的设计理念,默认不会记录日志。

这些异常都继承自一个基础的 IntrinsicException 类(该类在 @nestjs/common 包中导出),它帮助框架区分“正常业务异常”和“真正的系统错误异常”。这使得 Nest 能够智能地过滤日志,保证开发者关注的重点放在意外错误和系统故障上。

如果要记录这些异常,可以创建自定义异常过滤器

自定义异常

在实际开发中,Nest 提供了丰富的内置 HTTP 异常(如 BadRequestExceptionNotFoundExceptionForbiddenException 等),大多数情况下你完全可以直接使用它们,无需自己编写新的异常类。

但是,如果你的业务逻辑有特殊需求,或者想实现更符合项目语义的异常类型,建议你创建自定义异常类。为了保证 Nest 框架能够正确识别和处理这些异常,你的自定义异常最好继承自 HttpException 基类。

这样做的好处是:

  • 自定义异常会被 Nest 的内置异常过滤器自动捕获和转换成标准的 HTTP 错误响应。
  • 你可以自由定义异常的消息和状态码,灵活表达业务语义。
  • 保持与 Nest 异常体系的兼容,方便后续扩展和维护。

下面是一个简单的示例,展示如何定义一个自定义异常 ForbiddenException,并在控制器中抛出它:

ts
// forbidden.exception.ts
import { HttpException, HttpStatus } from '@nestjs/common';

export class ForbiddenException extends HttpException {
  constructor(message: string='Forbidden') {
    super(message, HttpStatus.FORBIDDEN);
  }
}

然后在 CatsController 中使用:

ts
@Get()
async findAll() {
  throw new ForbiddenException();
}

优先使用内置异常类:Nest 提供了大多数常见的异常类,功能完善且易于理解。

自定义异常适度使用:仅在业务需求明确且内置异常不足以表达时,才创建自定义异常。

继承自 HttpException:确保自定义异常能被 Nest 正确识别和处理。

可扩展性考虑:你可以为自定义异常添加额外字段(如错误码、错误详情等),方便前后端统一错误处理。

内置 HTTP 异常

Nest 提供了一组继承自基 HttpException 的标准异常。这些是从 @nestjs/common 包中公开的,代表了许多最常见的 HTTP 异常:

异常类名状态码含义(简要说明)
BadRequestException400请求无效(参数错误、格式不符等)
UnauthorizedException401未授权(通常用于身份验证失败)
ForbiddenException403禁止访问(已认证但无权限)
NotFoundException404请求的资源不存在
MethodNotAllowedException405请求方法不被允许
NotAcceptableException406服务端无法生成客户端可接受的响应
RequestTimeoutException408请求超时
ConflictException409请求与当前状态冲突(如用户名已存在)
GoneException410请求的资源已永久删除,不再可用
PayloadTooLargeException413请求体太大
UnsupportedMediaTypeException415不支持的媒体类型
UnprocessableEntityException422请求格式正确,但无法处理(如验证失败)
PreconditionFailedException412请求头中的前提条件未满足
InternalServerErrorException500服务器内部错误
NotImplementedException501功能未实现
BadGatewayException502网关错误
ServiceUnavailableException503服务不可用
GatewayTimeoutException504网关超时
HttpVersionNotSupportedException505不支持的 HTTP 版本
ImATeapotException418I'm a teapot:愚人节彩蛋,用于娱乐

所有这些内置异常都支持额外的 options 参数,可以用于增强调试信息:

ts
throw new BadRequestException('Something bad happened', {
  cause: new Error('Validation failed'),
  description: 'The user input did not pass validation',
});

响应示例:

ts
{
  "statusCode": 400,
  "message": "Something bad happened",
  "error": "The user input did not pass validation"
}

内置HTTP异常和HttpException的区别:

ts
  throw new HttpException('这是一个自定义错误', HttpStatus.FORBIDDEN, {
      cause: new Error('This is a custom message'),
      description: 'dad',
    });
/*
{
    "statusCode": 403,
    "message": "这是一个自定义错误"
}
*/
ts
 throw new BadRequestException('这是一个自定义错误', {
      cause: new Error('This is a custom message'),
      description: '错误描述',
    });
/*
{
    "message": "这是一个自定义错误",
    "error": "错误描述",
    "statusCode": 400
}
*/

核心区别总结

特性/行为HttpException(基类)内置异常(如 BadRequestException
默认响应格式{ statusCode, message }{ statusCode, message, error }
是否自带 error 字段❌ 默认不带,除非你手动设置整个响应体✅ 自动带有 error 字段,内容为 description 或默认字符串
构造函数new HttpException(response, status, options?)new XxxException(message, options?)(内部已包装 status & error)
实际使用建议更适合构建自定义结构的异常,或构建抽象层推荐直接使用,结构统一,符合 HTTP 标准
Nest 默认行为支持✅ 完全受支持✅ 更推荐使用(更结构化、语义清晰)
可继承性✅ 可作为你自定义异常的基类✅ 也可继承,但通常用于直接使用
是否带默认 description 信息❌ 没有默认✅ 有,比如 Bad RequestForbidden

异常过滤器

虽然基本(内置)异常过滤器可以自动为你处理许多情况,但你可能希望完全控制异常层,不满足于默认的 500 错误、或者只是显示一段 message,比如给出统一格式的错误响应,把错误写入日志,带上错误发生时的 URL、时间、请求信息等,捕获特定类型的异常用不同方式处理。

这时候就可以写一个 ExceptionFilter(异常过滤器)自定义异常处理逻辑

ts
// http-exception.filter.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(); // 针对 HTTP 请求(也有 RPC / WS)
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();
    const exceptionResponse = exception.getResponse();

    const error =
      typeof exceptionResponse === 'string'
        ? { message: exceptionResponse }
        : (exceptionResponse as object);

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      ...error,
    });
  }
}

@Catch(HttpException):只捕获 HttpException 类型的异常。你也可以写多个类型。

@Catch(HttpException) 装饰器将所需的元数据绑定到异常过滤器,告诉 Nest 这个特定的过滤器正在寻找 HttpException 类型的异常,而不是其他任何东西。@Catch() 装饰器可以采用单个参数或逗号分隔的列表。这使你可以一次为多种类型的异常设置过滤器。

ts
@Catch(BadRequestException, ForbiddenException)

如果你写:@Catch()就代表这个过滤器能捕获所有异常。

ArgumentsHost是一个 抽象适配器,因为 Nest 不只是支持 HTTP,也支持 GraphQL、WebSocket、gRPC 等,而你通过 host.switchToHttp() 就能获取 HTTP 请求相关信息:

ts
const ctx = host.switchToHttp();
const request = ctx.getRequest<Request>();
const response = ctx.getResponse<Response>();

默认错误响应太简陋或不统一,通过自定义异常过滤器就能输出更友好的响应结构:

ts
{
  "statusCode": 400,
  "message": "参数错误",
  "timestamp": "2025-07-12T10:30:00Z",
  "path": "/api/user/register"
}

Nest 有两种 message:

ts
throw new BadRequestException('错误提示');

throw new BadRequestException({
  message: '参数错误',
  error: '验证失败'
});

所以你要判断 getResponse() 的类型是 string 还是 object,然后做不同处理。

如果你使用 @nestjs/platform-fastify,则可以使用 response.send() 而不是 response.json()。不要忘记从 fastify 导入正确的类型。

ts
const response = ctx.getResponse<FastifyReply>();
response.status(status).send({...})

所有异常过滤器都应实现通用 ExceptionFilter<T> 接口。这要求你提供 catch(exception: T, host: ArgumentsHost) 方法及其指示的签名。T 表示异常的类型。

参数主机

catch() 方法的参数:

exception 参数是当前正在处理的异常对象,即被抛出的异常对象,通常是:HttpException(比如 ForbiddenExceptionBadRequestException 等),或你自己定义继承它的异常类

你可以调用:.getStatus():获取 HTTP 状态码,.getResponse():获取传给异常构造函数的 response 对象(可以是字符串或对象)

ts
throw new HttpException({
  status: HttpStatus.FORBIDDEN,
  error: 'Access denied',
}, HttpStatus.FORBIDDEN);
ts
const responseBody = exception.getResponse();
// 结果为:{ status: 403, error: 'Access denied' }

exception.message获取的通常是字符串类型,是构造函数传入的 response,如果是对象则为 [object Object]

ArgumentsHostNestJS 提供的一个上下文适配器,它用于抽象化访问当前请求上下文中的原始参数,比如:

HTTP 请求中的 RequestResponse

WebSocket 中的 ClientPayload

RPC 调用中的 DataContext

它的核心作用就是让你不依赖具体协议(如 Express/Fastify/WebSocket)就能获取参数,从而写出可复用、协议无关的代码。

使用ArgumentsHost的原因是:NestJS 是一个多协议支持的框架,HTTP(Express/Fastify),WebSockets(Socket.io)Microservices(gRPC, Kafka, NATS 等),每种协议的底层 原始参数 都不一样

协议原始参数
HTTPreq, res, next
WSclient, data
RPCdata, context

但代码不可能每种都重写一遍啊?所以 NestJS 提供了一个抽象,ArgumentsHost:通过它,你可以「切换」到你需要的上下文(HTTP/WebSocket/RPC),然后获取对应的参数。

ts
const ctx = host.switchToHttp(); // 也可以是 .switchToWs()、.switchToRpc()

ArgumentsHost 的常用方法

方法作用
getType()获取当前上下文类型(http / ws / rpc)
switchToHttp()切换到 HTTP 上下文
switchToWs()切换到 WebSocket 上下文
switchToRpc()切换到 RPC 上下文
getArgs()获取当前处理函数的所有参数(数组)
getArgByIndex(index)获取特定参数(常用于低层自定义场景)

跨协议支持

ts
const type = host.getType();

if (type === 'http') {
  const ctx = host.switchToHttp();
  // ...
} else if (type === 'ws') {
  const ctx = host.switchToWs();
  // ...
}

总结ArgumentsHost 是 NestJS 提供的跨协议上下文访问工具,配合 switchToHttp() 等方法,你可以安全地在不同协议中访问正确的请求参数,让异常过滤器、守卫、拦截器等逻辑具备协议无关性。

绑定过滤器

方法级绑定

ts
@Post()
@UseFilters(HttpExceptionFilter) // 绑定到这个 create() 方法
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}

@UseFilters() 装饰器是从 @nestjs/common 包导入的,用于将自定义的异常过滤器绑定到某个处理函数。

在这个例子中,只要 create() 方法中抛出 HttpException 或其子类,Nest 就会交给 HttpExceptionFilter 来处理。

推荐用 类名HttpExceptionFilter),而不是用 new HttpExceptionFilter(),这样 Nest 可以复用实例,也支持依赖注入。

@UseFilters() 装饰器,类似于 @Catch() 装饰器,它可以采用单个过滤器实例,或以逗号分隔的过滤器实例列表。在这里,我们就地创建了 HttpExceptionFilter 的实例,或者,你可以传递类(而不是实例), 将实例化的责任留给框架,并启用依赖注入。

建议:尽可能使用类而不是实例来应用过滤器。它减少了内存使用量,因为 Nest 可以轻松地在整个模块中重用同一类的实例。

控制器级绑定

ts
@Controller('cats')
@UseFilters(HttpExceptionFilter)
export class CatsController {
  @Get()
  findAll() { /*...*/ }

  @Post()
  create() { /*...*/ }
}

在控制器上使用 @UseFilters(),可以让该控制器内的 所有路由处理器 都应用这个异常过滤器。

使用场景:比如一个模块中所有 API 都有统一错误响应格式,就适合绑定在控制器上。

全局绑定(覆盖所有控制器)

ts
// main.ts
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());

从任何模块外部注册的全局过滤器不能注入依赖,因为这是在任何模块的上下文之外完成的。为了解决此问题,你可以使用以下结构直接从任何模块注册全局作用域的过滤器:

推荐的方式(支持依赖注入):

ts
// app.module.ts
import { APP_FILTER } from '@nestjs/core';

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

全局绑定意味着:任何模块、控制器、方法中抛出的异常,都会被这个过滤器处理。

如果你希望统一处理整个应用的错误响应结构,推荐使用这种方式。

通过 APP_FILTER 注册的方式支持依赖注入,而 app.useGlobalFilters() 不支持注入服务。

当你通过这种方式(也就是 { provide: APP_FILTER, useClass: XxxFilter })来注册一个支持依赖注入的过滤器时,要注意:

不管你在哪个模块中这样注册,这个过滤器都会被视为“全局过滤器”。

那这种注册应该放在哪个模块中比较好呢?

建议放在定义这个过滤器的模块中(比如上面的示例中是 HttpExceptionFilter 所在模块)。

注意useClass 并不是定义自定义提供器的唯一方法,Nest 还支持其他形式的注册方式

你也可以用相同的方式同时注册多个过滤器,只需把每一个都放进 providers 数组中即可。

app.useGlobalFilters() 只会对 HTTP 请求生效不会作用于 WebSocket 网关、GraphQL、或混合类型的应用上下文,也就是它只会应用在 HTTP 上下文中(也就是控制器 Controller 的异常处理中生效)。

如果你还想让它对 WebSocket 生效,你必须在WebSocket 网关类中单独设置过滤器

ts
@WebSocketGateway()
@UseFilters(new YourExceptionFilter())
export class YourGateway {
  // ...
}

捕获一切

为了捕获所有未处理的异常,只需在 @Catch() 装饰器中不填任何参数,就像给一个空篮子,让它接住所有掉下来的“苹果”。以下示例展示了一个与平台无关的代码片段,它通过 HTTP 适配器处理响应,巧妙避开了直接使用特定平台的 RequestResponse 对象,像个“跨界外交官”一样灵活:

typescript
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
} from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';

@Catch()
export class CatchEverythingFilter implements ExceptionFilter {
  constructor(private readonly httpAdapterHost: HttpAdapterHost) {}

  catch(exception: unknown, host: ArgumentsHost): void {
    // 有时候在构造函数中 httpAdapter 可能还没准备好,所以我们在这里动态获取
    const { httpAdapter } = this.httpAdapterHost;

    const ctx = host.switchToHttp();

    // 判断异常是否为 HttpException,若是则获取其状态码,否则默认返回 500
    const httpStatus =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    // 构造响应体,包含状态码、时间戳和请求路径
    const responseBody = {
      statusCode: httpStatus,
      timestamp: new Date().toISOString(),
      path: httpAdapter.getRequestUrl(ctx.getRequest()),
    };

    // 通过适配器发送响应
    httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
  }
}

注意:当你将“捕获所有”的异常过滤器与专门针对特定异常类型的过滤器一起使用时,记得把 @Catch() 放在最后,就像让“专业选手”先处理特定任务,杂活留给“全能选手”来兜底。

常见陷阱

  • HttpAdapter 不可用:如代码注释所述,httpAdapter 在构造函数中可能未初始化,因此在 catch 方法中动态获取是更安全的做法。
  • 过滤器顺序:如果 @Catch() 放在特定类型过滤器(如 @Catch(HttpException)) 之前,可能会导致特定过滤器失效。就像让“全能选手”抢了“专业选手”的活。
  • 性能问题:捕获所有异常可能会掩盖代码中的潜在问题,建议仅在全局使用,并在开发阶段配合日志分析具体异常。

API 补充

  • HttpAdapterHost:提供对底层 HTTP 适配器的访问(如 Express 或 Fastify),通过 httpAdapter.getRequestUrl() 获取请求路径。
  • ArgumentsHost:NestJS 的上下文对象,包含请求、响应等信息,通过 switchToHttp() 切换到 HTTP 上下文。
  • httpAdapter.reply:发送响应,参数分别是响应对象、响应体和状态码。

继承

通常,你会打造一个完全定制的异常过滤器来满足应用的独特需求,但有时候,你可能只想在 NestJS 内置的默认全局异常过滤器(BaseExceptionFilter)上稍作调整,为了将异常处理的任务交给基础过滤器,你需要扩展 BaseExceptionFilter 并调用它自带的 catch() 方法。

以下是一个示例,展示如何扩展默认过滤器:

typescript
import { Catch, ArgumentsHost } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';

@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    // 调用父类的 catch 方法,复用内置异常处理逻辑
    super.catch(exception, host);
  }
}

注意:对于方法范围或控制器范围的过滤器,扩展 BaseExceptionFilter 时,千万不要用 new 手动实例化。让 NestJS 框架自动处理实例化。

全局过滤器也可以扩展 BaseExceptionFilter,这可以通过两种方式实现:

方式一:注入 HttpAdapter

在实例化自定义全局过滤器时,手动注入 HttpAdapter 引用,确保过滤器能与底层平台(如 Express 或 Fastify)无缝协作:

typescript
async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const { httpAdapter } = app.get(HttpAdapterHost);
  // 注入 httpAdapter,确保跨平台兼容
  app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));

  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

方式二:使用 APP_FILTER 令牌

通过 NestJS 的依赖注入系统,使用 APP_FILTER 令牌注册全局过滤器。

ts
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { AllExceptionsFilter } from './all-exceptions.filter';

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: AllExceptionsFilter,	// 此处可以不用写参数是因为在模块上下文中,自动进行类型依赖注入了
    },
  ],
})
export class AppModule {}

核心代码:AllExceptionsFilter

typescript
import { Catch, ArgumentsHost } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';

@Catch() // 空参数表示捕获所有异常
export class AllExceptionsFilter extends BaseExceptionFilter {
  constructor(httpAdapter: HttpAdapterHost) {
    super(httpAdapter.httpAdapter); // 传递 httpAdapter 给父类
  }

  catch(exception: unknown, host: ArgumentsHost) {
    // 直接调用父类的 catch 方法,复用默认异常处理逻辑
    super.catch(exception, host);
  }
}

代码解释

  • @Catch():不带参数,表示捕获所有类型的异常,类似于前面讨论的“万能捕手”。
  • extends BaseExceptionFilter:继承 NestJS 内置的全局异常过滤器,复用其默认行为(如处理 HttpException 并返回格式化的错误响应)。
  • super.catch(exception, host):调用父类的 catch 方法,确保内置逻辑(如状态码处理、响应格式化)继续生效。你可以在这里添加自定义逻辑,比如记录日志或修改响应。
  • 构造函数中的 httpAdapterBaseExceptionFilter 需要 httpAdapter 来与底层平台交互,必须通过构造函数传递。

复杂部分:全局注册

方式一的代码展示了如何手动注入 HttpAdapter

typescript
const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));

解释

  • app.get(HttpAdapterHost):从 NestJS 容器中获取 HttpAdapterHost 实例,包含底层的 HTTP 适配器(如 Express 或 Fastify 的实例)。
  • new AllExceptionsFilter(httpAdapter):将 httpAdapter 传递给过滤器的构造函数,确保过滤器能访问底层平台的响应机制。
  • app.useGlobalFilters:将过滤器注册为全局,捕获应用中所有未处理的异常。

补充解释

  • BaseExceptionFilter 是 NestJS 提供的内置全局异常过滤器,负责处理常见异常(如 HttpException)并返回标准化的错误响应。
  • 扩展 BaseExceptionFilter 允许你在保留默认行为的基础上,添加自定义逻辑,比如记录错误、修改响应格式或处理特定异常。

使用场景

  • 统一错误格式:扩展 BaseExceptionFilter 适合需要对默认错误响应稍作调整的场景,例如添加自定义字段到响应体。
  • 跨平台兼容:通过注入 HttpAdapter,过滤器可以适配 Express、Fastify 等不同底层平台。
  • 全局兜底:与 @Catch() 结合,适合作为全局异常处理的最后防线。

常见陷阱

  • 手动实例化:手动用 new 实例化方法或控制器范围的过滤器可能导致依赖注入失败。始终通过 NestJS 的 IoC 容器管理实例。
  • HttpAdapter 未注入:如果忘记注入 httpAdapter,过滤器可能无法正确处理响应,抛出运行时错误。
  • 过度依赖父类:完全依赖 super.catch 可能限制灵活性。如果需要大幅定制响应逻辑,可能需要直接实现 ExceptionFilter 接口。

API 补充

  • BaseExceptionFilter:NestJS 核心模块提供的内置过滤器,默认处理 HttpException 和部分内置异常,返回 JSON 格式的错误响应。
  • HttpAdapterHost:提供对底层 HTTP 适配器的访问,方法如 getRequestUrlreply 可用于获取请求信息或发送响应。
  • APP_FILTER:NestJS 的令牌,用于通过依赖注入注册全局过滤器。

说明:通过 APP_FILTER 令牌,NestJS 会自动实例化 AllExceptionsFilter,并注入 HttpAdapterHost,避免手动实例化的麻烦。

复用父类逻辑(前置修改)

typescript
catch(exception: unknown, host: ArgumentsHost) {
    console.error('Caught exception:', exception); // 记录日志

    // 包装异常,添加自定义信息
    const wrappedException =
      exception instanceof HttpException
        ? exception
        : new HttpException(
            {
              statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
              message: 'Something went wrong',
              timestamp: new Date().toISOString(),
              path: this.httpAdapter.getRequestUrl(host.switchToHttp().getRequest()),
            },
            HttpStatus.INTERNAL_SERVER_ERROR,
          );

    // 交给父类处理
    super.catch(wrappedException, host);
  }

优化错误信息

为生产环境提供用户友好的错误信息,避免暴露敏感信息:

typescript
catch(exception: unknown, host: ArgumentsHost) {
  const ctx = host.switchToHttp();
  const status =
    exception instanceof HttpException
      ? exception.getStatus()
      : HttpStatus.INTERNAL_SERVER_ERROR;
  const responseBody = {
    statusCode: status,
    timestamp: new Date().toISOString(),
    path: this.httpAdapter.getRequestUrl(ctx.getRequest()),
    message: status === HttpStatus.INTERNAL_SERVER_ERROR
      ? 'Internal server error'
      : exception instanceof HttpException
        ? exception.message
        : 'Unknown error',
  };
  this.httpAdapter.reply(ctx.getResponse(), responseBody, status);
}

最后更新时间: