NestJS 异常过滤器
目录
异常过滤器
Nest 内置了一个异常处理层,负责捕获应用中所有未被显式处理的异常。当你的代码中出现异常却没有被 try-catch 捕获时,异常过滤器会自动接手,保证程序不会崩溃,同时给前端返回一个格式统一且易理解的错误信息。
默认情况下,Nest 使用一个全局异常过滤器,专门处理 HttpException
及其派生类(例如 404 找不到资源、401 未授权等),它会将这些异常转换成标准的 HTTP 响应。对于那些不属于 HttpException
的错误(比如运行时错误、数据库连接异常等):
throw new Error('This is a test error');
这个过滤器会返回一个通用的 500 状态码和“Internal server error”的信息,避免把内部细节暴露给客户端,提升安全性。
{
"statusCode": 500,
"message": "Internal server error"
}
此外,Nest 对第三方库 http-errors 有一定的兼容支持。如果你抛出的异常对象里带有 statusCode
和 message
这两个属性,Nest 也会识别它们,并把它们原样返回,而不会一律替换成默认的 500 错误,这让你在使用其他库时也能保持一致的错误处理体验。
throw { statusCode: 400, message: 'This is a test error' };
抛出标准异常
Nest 提供了一个内置的异常类 HttpException
,它在 @nestjs/common
包中导出。对于基于 HTTP 的 REST 或 GraphQL API 应用,推荐的做法是在出现错误时,通过抛出标准的 HTTP 异常来统一响应错误。
举个例子,假设在 CatsController
中有一个处理 GET 请求的 findAll()
方法。如果这个方法出现错误,我们可以直接抛出一个 HttpException
来表示访问被禁止:
@Get()
async findAll() {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
这里的 HttpStatus
是从 @nestjs/common
导入的一个枚举,包含了 HTTP 的各种状态码。
当客户端调用这个接口时,服务器会返回如下 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 响应里,但可以用来做日志记录或调试,主要用于内部错误追踪。
@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 是:
{
"status": 403,
"error": "This is a custom message"
}
cause
仍可在服务器端访问,方便你将详细异常写入日志或做后续处理。
options
还支持一个 description
字段,它是一个字符串,用于描述异常的详细信息,它也会被序列化到 HTTP 响应里,通常用于自定义的错误描述信息,可以在你自定义异常过滤器或日志处理逻辑里读取,用来提供更详细的上下文。
HttpException
构造函数的TypeScript类型定义如下:
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 异常(如 BadRequestException
、NotFoundException
、ForbiddenException
等),大多数情况下你完全可以直接使用它们,无需自己编写新的异常类。
但是,如果你的业务逻辑有特殊需求,或者想实现更符合项目语义的异常类型,建议你创建自定义异常类。为了保证 Nest 框架能够正确识别和处理这些异常,你的自定义异常最好继承自 HttpException
基类。
这样做的好处是:
- 自定义异常会被 Nest 的内置异常过滤器自动捕获和转换成标准的 HTTP 错误响应。
- 你可以自由定义异常的消息和状态码,灵活表达业务语义。
- 保持与 Nest 异常体系的兼容,方便后续扩展和维护。
下面是一个简单的示例,展示如何定义一个自定义异常 ForbiddenException
,并在控制器中抛出它:
// forbidden.exception.ts
import { HttpException, HttpStatus } from '@nestjs/common';
export class ForbiddenException extends HttpException {
constructor(message: string='Forbidden') {
super(message, HttpStatus.FORBIDDEN);
}
}
然后在 CatsController
中使用:
@Get()
async findAll() {
throw new ForbiddenException();
}
优先使用内置异常类:Nest 提供了大多数常见的异常类,功能完善且易于理解。
自定义异常适度使用:仅在业务需求明确且内置异常不足以表达时,才创建自定义异常。
继承自 HttpException
:确保自定义异常能被 Nest 正确识别和处理。
可扩展性考虑:你可以为自定义异常添加额外字段(如错误码、错误详情等),方便前后端统一错误处理。
内置 HTTP 异常
Nest 提供了一组继承自基 HttpException
的标准异常。这些是从 @nestjs/common
包中公开的,代表了许多最常见的 HTTP 异常:
异常类名 | 状态码 | 含义(简要说明) |
---|---|---|
BadRequestException | 400 | 请求无效(参数错误、格式不符等) |
UnauthorizedException | 401 | 未授权(通常用于身份验证失败) |
ForbiddenException | 403 | 禁止访问(已认证但无权限) |
NotFoundException | 404 | 请求的资源不存在 |
MethodNotAllowedException | 405 | 请求方法不被允许 |
NotAcceptableException | 406 | 服务端无法生成客户端可接受的响应 |
RequestTimeoutException | 408 | 请求超时 |
ConflictException | 409 | 请求与当前状态冲突(如用户名已存在) |
GoneException | 410 | 请求的资源已永久删除,不再可用 |
PayloadTooLargeException | 413 | 请求体太大 |
UnsupportedMediaTypeException | 415 | 不支持的媒体类型 |
UnprocessableEntityException | 422 | 请求格式正确,但无法处理(如验证失败) |
PreconditionFailedException | 412 | 请求头中的前提条件未满足 |
InternalServerErrorException | 500 | 服务器内部错误 |
NotImplementedException | 501 | 功能未实现 |
BadGatewayException | 502 | 网关错误 |
ServiceUnavailableException | 503 | 服务不可用 |
GatewayTimeoutException | 504 | 网关超时 |
HttpVersionNotSupportedException | 505 | 不支持的 HTTP 版本 |
ImATeapotException | 418 | I'm a teapot:愚人节彩蛋,用于娱乐 |
所有这些内置异常都支持额外的 options
参数,可以用于增强调试信息:
throw new BadRequestException('Something bad happened', {
cause: new Error('Validation failed'),
description: 'The user input did not pass validation',
});
响应示例:
{
"statusCode": 400,
"message": "Something bad happened",
"error": "The user input did not pass validation"
}
内置HTTP异常和HttpException的区别:
throw new HttpException('这是一个自定义错误', HttpStatus.FORBIDDEN, {
cause: new Error('This is a custom message'),
description: 'dad',
});
/*
{
"statusCode": 403,
"message": "这是一个自定义错误"
}
*/
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 Request 、Forbidden 等 |
异常过滤器
虽然基本(内置)异常过滤器可以自动为你处理许多情况,但你可能希望完全控制异常层,不满足于默认的 500 错误、或者只是显示一段 message
,比如给出统一格式的错误响应,把错误写入日志,带上错误发生时的 URL、时间、请求信息等,捕获特定类型的异常用不同方式处理。
这时候就可以写一个 ExceptionFilter(异常过滤器) 来 自定义异常处理逻辑。
// 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()
装饰器可以采用单个参数或逗号分隔的列表。这使你可以一次为多种类型的异常设置过滤器。
@Catch(BadRequestException, ForbiddenException)
如果你写:@Catch()
就代表这个过滤器能捕获所有异常。
ArgumentsHost
是一个 抽象适配器,因为 Nest 不只是支持 HTTP,也支持 GraphQL、WebSocket、gRPC 等,而你通过 host.switchToHttp()
就能获取 HTTP 请求相关信息:
const ctx = host.switchToHttp();
const request = ctx.getRequest<Request>();
const response = ctx.getResponse<Response>();
默认错误响应太简陋或不统一,通过自定义异常过滤器就能输出更友好的响应结构:
{
"statusCode": 400,
"message": "参数错误",
"timestamp": "2025-07-12T10:30:00Z",
"path": "/api/user/register"
}
Nest 有两种 message:
throw new BadRequestException('错误提示');
throw new BadRequestException({
message: '参数错误',
error: '验证失败'
});
所以你要判断 getResponse()
的类型是 string 还是 object,然后做不同处理。
如果你使用 @nestjs/platform-fastify
,则可以使用 response.send()
而不是 response.json()
。不要忘记从 fastify
导入正确的类型。
const response = ctx.getResponse<FastifyReply>();
response.status(status).send({...})
所有异常过滤器都应实现通用 ExceptionFilter<T>
接口。这要求你提供 catch(exception: T, host: ArgumentsHost)
方法及其指示的签名。T
表示异常的类型。
参数主机
catch()
方法的参数:
exception
参数是当前正在处理的异常对象,即被抛出的异常对象,通常是:HttpException
(比如 ForbiddenException
、BadRequestException
等),或你自己定义继承它的异常类
你可以调用:.getStatus()
:获取 HTTP 状态码,.getResponse()
:获取传给异常构造函数的 response
对象(可以是字符串或对象)
throw new HttpException({
status: HttpStatus.FORBIDDEN,
error: 'Access denied',
}, HttpStatus.FORBIDDEN);
const responseBody = exception.getResponse();
// 结果为:{ status: 403, error: 'Access denied' }
exception.message
获取的通常是字符串类型,是构造函数传入的 response,如果是对象则为 [object Object]
ArgumentsHost
是 NestJS 提供的一个上下文适配器,它用于抽象化访问当前请求上下文中的原始参数,比如:
HTTP 请求中的 Request
、Response
;
WebSocket 中的 Client
、Payload
;
RPC 调用中的 Data
、Context
。
它的核心作用就是让你不依赖具体协议(如 Express/Fastify/WebSocket)就能获取参数,从而写出可复用、协议无关的代码。
使用ArgumentsHost
的原因是:NestJS 是一个多协议支持的框架,HTTP(Express/Fastify),WebSockets(Socket.io)Microservices(gRPC, Kafka, NATS 等),每种协议的底层 原始参数 都不一样
协议 | 原始参数 |
---|---|
HTTP | req , res , next |
WS | client , data |
RPC | data , context |
但代码不可能每种都重写一遍啊?所以 NestJS 提供了一个抽象,ArgumentsHost
:通过它,你可以「切换」到你需要的上下文(HTTP/WebSocket/RPC),然后获取对应的参数。
const ctx = host.switchToHttp(); // 也可以是 .switchToWs()、.switchToRpc()
ArgumentsHost 的常用方法
方法 | 作用 |
---|---|
getType() | 获取当前上下文类型(http / ws / rpc) |
switchToHttp() | 切换到 HTTP 上下文 |
switchToWs() | 切换到 WebSocket 上下文 |
switchToRpc() | 切换到 RPC 上下文 |
getArgs() | 获取当前处理函数的所有参数(数组) |
getArgByIndex(index) | 获取特定参数(常用于低层自定义场景) |
跨协议支持:
const type = host.getType();
if (type === 'http') {
const ctx = host.switchToHttp();
// ...
} else if (type === 'ws') {
const ctx = host.switchToWs();
// ...
}
总结:ArgumentsHost
是 NestJS 提供的跨协议上下文访问工具,配合 switchToHttp()
等方法,你可以安全地在不同协议中访问正确的请求参数,让异常过滤器、守卫、拦截器等逻辑具备协议无关性。
绑定过滤器
方法级绑定
@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 可以轻松地在整个模块中重用同一类的实例。
控制器级绑定
@Controller('cats')
@UseFilters(HttpExceptionFilter)
export class CatsController {
@Get()
findAll() { /*...*/ }
@Post()
create() { /*...*/ }
}
在控制器上使用 @UseFilters()
,可以让该控制器内的 所有路由处理器 都应用这个异常过滤器。
使用场景:比如一个模块中所有 API 都有统一错误响应格式,就适合绑定在控制器上。
全局绑定(覆盖所有控制器)
// main.ts
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
从任何模块外部注册的全局过滤器不能注入依赖,因为这是在任何模块的上下文之外完成的。为了解决此问题,你可以使用以下结构直接从任何模块注册全局作用域的过滤器:
推荐的方式(支持依赖注入):
// 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 网关类中单独设置过滤器。
@WebSocketGateway()
@UseFilters(new YourExceptionFilter())
export class YourGateway {
// ...
}
捕获一切
为了捕获所有未处理的异常,只需在 @Catch()
装饰器中不填任何参数,就像给一个空篮子,让它接住所有掉下来的“苹果”。以下示例展示了一个与平台无关的代码片段,它通过 HTTP 适配器处理响应,巧妙避开了直接使用特定平台的 Request
或 Response
对象,像个“跨界外交官”一样灵活:
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()
方法。
以下是一个示例,展示如何扩展默认过滤器:
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)无缝协作:
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
令牌注册全局过滤器。
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
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
方法,确保内置逻辑(如状态码处理、响应格式化)继续生效。你可以在这里添加自定义逻辑,比如记录日志或修改响应。- 构造函数中的
httpAdapter
:BaseExceptionFilter
需要httpAdapter
来与底层平台交互,必须通过构造函数传递。
复杂部分:全局注册
方式一的代码展示了如何手动注入 HttpAdapter
:
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 适配器的访问,方法如getRequestUrl
和reply
可用于获取请求信息或发送响应。APP_FILTER
:NestJS 的令牌,用于通过依赖注入注册全局过滤器。
说明:通过 APP_FILTER
令牌,NestJS 会自动实例化 AllExceptionsFilter
,并注入 HttpAdapterHost
,避免手动实例化的麻烦。
复用父类逻辑(前置修改)
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);
}
优化错误信息
为生产环境提供用户友好的错误信息,避免暴露敏感信息:
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);
}