异常过滤器
异常过滤器
Nest 带有一个内置的异常层,负责处理应用中所有未处理的异常。当你的应用代码未处理异常时,该层会捕获该异常,然后自动发送适当的用户友好响应。

开箱即用,此操作由内置的全局异常过滤器执行,该过滤器处理 HttpException 类型(及其子类)的异常。当异常无法识别时(既不是 HttpException 也不是继承自 HttpException 的类),内置异常过滤器会生成以下默认 JSON 响应
{
"statusCode": 500,
"message": "Internal server error"
}注意
全局异常过滤器部分支持 http-errors 库。基本上,任何包含 statusCode 和 message 属性的抛出异常都将被正确填充并作为响应发回(而不是用于无法识别的异常的默认 InternalServerErrorException)。
抛出标准异常
Nest 提供了一个内置的 HttpException 类,从 @nestjs/common 包中暴露出来。对于典型的基于 HTTP REST/GraphQL API 的应用,最佳做法是在发生某些错误情况时发送标准 HTTP 响应对象。
例如,在 AppController 中,我们有一个 findAll() 方法(一个 GET 路由处理程序)。假设此路由处理程序出于某种原因抛出异常。为了证明这一点,我们将硬编码如下
@Get()
async findAll() {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}案例如下:
// app.controller.ts
import { Controller, Get, HttpException, HttpStatus } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
async findAll() {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
}访问路径:http://127.0.0.1:3000/, 调用异常结果如下:

注意:这里使用的是 HttpStatus。这是从 @nestjs/common 包导入的辅助枚举。
当客户端调用此端点时,响应如下所示
{
"statusCode": 403,
"message": "Forbidden"
}HttpException 构造函数采用两个必需的参数来确定响应:
response参数定义 JSON 响应主体。它可以是string或object,如下所述。status参数定义了 HTTP 状态代码。
默认情况下,JSON 响应主体包含两个属性:
statusCode:默认为status参数中提供的 HTTP 状态代码message:基于status的 HTTP 错误的简短描述
要仅覆盖 JSON 响应正文的消息部分,请在 response 参数中提供一个字符串。要覆盖整个 JSON 响应主体,请在 response 参数中传递一个对象。Nest 将序列化该对象并将其作为 JSON 响应主体返回。
第二个构造函数参数 - status - 应该是有效的 HTTP 状态代码。最佳做法是使用从 @nestjs/common 导入的 HttpStatus 枚举。
有第三个构造函数参数(可选) - options - 可用于提供错误 cause。此 cause 对象未序列化到响应对象中,但它可用于记录目的,提供有关导致 HttpException 被抛出的内部错误的有价值信息。
这是一个覆盖整个响应主体并提供错误原因的示例
// app.controller.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
});
}
}使用上面的内容,这就是响应的样子:
{
"status": 403,
"error": "This is a custom message"
}异常日志记录
默认情况下,异常过滤器不会记录内置异常(如 HttpException)(以及从它继承的任何异常)。抛出这些异常时,它们不会出现在控制台中,因为它们被视为正常应用流程的一部分。相同的行为适用于其他内置异常,如 WsException 和 RpcException。
这些异常都继承自基本 IntrinsicException 类,该类从 @nestjs/common 包中导出。此类有助于区分属于正常应用操作的异常和非正常应用操作的异常。
如果要记录这些异常,可以创建自定义异常过滤器
自定义异常过滤器
在许多情况下,你不需要编写自定义异常,并且可以使用内置的 Nest HTTP 异常,如下一节所述。如果你确实需要创建自定义异常,那么最好创建你自己的异常层次结构,其中你的自定义异常继承自 HttpException 基类。通过这种方法,Nest 将识别你的异常,并自动处理错误响应。让我们实现这样一个自定义异常
// forbidden.exception.ts
export class ForbiddenException extends HttpException {
constructor() {
super('Forbidden', HttpStatus.FORBIDDEN);
}
}由于 ForbiddenException 扩展了基 HttpException,它将与内置异常处理程序无缝协作,因此我们可以在 findAll() 方法中使用它
// app.controller.ts
@Get()
async findAll() {
throw new ForbiddenException();
}内置HTTP异常
Nest 提供了一组内置的 HTTP 异常,这些异常是从 @nestjs/common 包中导出的。这些异常是 HttpException 的子类,它们继承了 HttpException 的所有属性和方法。
以下是一些常用的内置 HTTP 异常:
- BadRequestException:用于表示请求无效或参数错误
- UnauthorizedException:用于表示未经授权的访问
- NotFoundException:用于表示请求的资源不存在
- ForbiddenException:用于表示访问被禁止
- NotAcceptableException:用于表示请求的资源不可接受
- RequestTimeoutException:用于表示请求超时
- ConflictException:用于表示请求与当前状态冲突
- GoneException:用于表示请求的资源已被删除
- HttpVersionNotSupportedException:用于表示请求的 HTTP 版本不受支持
- PayloadTooLargeException:用于表示请求的有效负载太大
- UnsupportedMediaTypeException:用于表示请求的媒体类型不受支持
- UnprocessableEntityException:用于表示请求的实体无法处理
- InternalServerErrorException:用于表示内部服务器错误
- NotImplementedException:用于表示请求的功能尚未实现
- ImATeapotException:用于表示请求的资源是茶壶
- MethodNotAllowedException:用于表示请求的方法不被允许
- BadGatewayException:用于表示网关错误
- ServiceUnavailableException:用于表示服务不可用
- GatewayTimeoutException:用于表示网关超时
- PreconditionFailedException:用于表示请求的前提条件失败
所有内置异常也可以使用 options 参数提供错误 cause 和错误描述:
throw new BadRequestException('Something bad happened', {
cause: new Error(),
description: 'Some error description',
});使用上面的内容,这就是响应的样子:
{
"message": "Something bad happened",
"error": "Some error description",
"statusCode": 400
}异常过滤器
虽然基本(内置)异常过滤器可以自动为你处理许多情况,但你可能希望完全控制异常层。例如,你可能希望根据某些动态因素添加日志记录或使用不同的 JSON 模式。异常过滤器正是为此目的而设计的。它们让你可以控制准确的控制流和发送回客户端的响应内容。
让我们创建一个异常过滤器,负责捕获作为 HttpException 类实例的异常,并为它们实现自定义响应逻辑。为此,我们需要访问底层平台 Request 和 Response 对象。我们将访问 Request 对象,以便提取原始 url 并将其包含在日志信息中。我们将使用 Response 对象直接控制发送的响应,使用 response.json() 方法。
// 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();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}提示
所有异常过滤器都应实现通用 ExceptionFilter<T> 接口。这要求你提供 catch(exception: T, host: ArgumentsHost) 方法及其指示的签名。T 表示异常的类型。
注意
如果你使用 @nestjs/platform-fastify,则可以使用 response.send() 而不是 response.json()。不要忘记从 fastify 导入正确的类型。
@Catch(HttpException) 装饰器将所需的元数据绑定到异常过滤器,告诉 Nest 这个特定的过滤器正在寻找 HttpException 类型的异常,而不是其他任何东西。@Catch() 装饰器可以采用单个参数或逗号分隔的列表。这使你可以一次为多种类型的异常设置过滤器。
参数主机
我们看一下 catch() 方法的参数。exception 参数是当前正在处理的异常对象。host 参数是一个 ArgumentsHost 对象。ArgumentsHost 是一个强大的实用程序对象,我们将在 执行上下文章节* 中进一步研究它。在此代码示例中,我们使用它来获取对传递给原始请求处理程序(在异常产生的控制器中)的 Request 和 Response 对象的引用。在此代码示例中,我们在 ArgumentsHost 上使用了一些辅助方法来获取所需的 Request 和 Response 对象。了解有关 ArgumentsHost此处 的更多信息。
绑定过滤器
单个控制器路径
要将异常过滤器绑定到特定的控制器路径,我们需要使用 @UseFilters() 装饰器。这是一个示例,说明如何将 HttpExceptionFilter 绑定到 AppController 的 create() 方法:
// app.controller.ts
import { Controller, ForbiddenException, Get, UseFilters } from '@nestjs/common';
import { AppService } from './app.service';
import { HttpExceptionFilter } from './http-exception/http-exception.filter';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
@UseFilters(new HttpExceptionFilter())
async getHello() {
throw new ForbiddenException();
}
}提示
@UseFilters() 装饰器是从 @nestjs/common 包导入的。
执行结果如下:

我们在这里使用了 @UseFilters() 装饰器。类似于 @Catch() 装饰器,它可以采用单个过滤器实例,或以逗号分隔的过滤器实例列表。在这里,我们就地创建了 HttpExceptionFilter 的实例。或者,你可以传递类(而不是实例),将实例化的责任留给框架,并启用依赖注入。
@Get()
@UseFilters(HttpExceptionFilter)
async getHello() {
throw new ForbiddenException();
}执行结果和上面一样:

提示
尽可能使用类而不是实例来应用过滤器。它减少了内存使用量,因为 Nest 可以轻松地在整个模块中重用同一类的实例。
控制器范围
你可以使用 @UseFilters() 装饰器将异常过滤器绑定到整个控制器。这是一个示例,说明如何将 HttpExceptionFilter 绑定到 AppController 的所有方法:
// app.controller.ts
import { Controller, ForbiddenException, Get, UseFilters } from '@nestjs/common';
import { AppService } from './app.service';
import { HttpExceptionFilter } from './http-exception/http-exception.filter';
@Controller()
@UseFilters(HttpExceptionFilter)
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
async getHello() {
throw new ForbiddenException();
}
}此构造为 AppController 中定义的每个路由处理程序设置 HttpExceptionFilter。
全局范围
要创建全局作用域的过滤器,你可以执行以下操作:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './http-exception/http-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();提示
useGlobalFilters() 方法不为网关或混合应用设置过滤器
全局作用域的过滤器用于整个应用,用于每个控制器和每个路由处理程序。在依赖注入方面,从任何模块外部注册的全局过滤器(如上例中的 useGlobalFilters())不能注入依赖,因为这是在任何模块的上下文之外完成的。为了解决此问题,你可以使用以下结构直接从任何模块注册全局作用域的过滤器:
// app.module.ts
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
})
export class AppModule {}提示
当使用此方法对过滤器执行依赖注入时,请注意,无论采用此构造的模块如何,过滤器实际上都是全局的。这应该在哪里完成?选择定义过滤器(上例中的 HttpExceptionFilter)的模块。此外,useClass 不是处理自定义提供程序注册的唯一方法。
你可以根据需要使用此技术添加任意数量的过滤器;只需将每个添加到提供程序数组即可。
捕获一切
为了捕获每个未处理的异常(无论异常类型如何),请将 @Catch() 装饰器的参数列表留空,例如 @Catch()。
在下面的示例中,我们有一个与平台无关的代码,因为它使用 HTTP 适配器 来传递响应,并且不直接使用任何特定于平台的对象(Request 和 Response):
// http-exception.filter.ts
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();
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);
}
}提示
当将捕获所有内容的异常过滤器与绑定到特定类型的过滤器组合时,应首先声明 "抓住任何东西" 过滤器,以允许特定过滤器正确处理绑定类型。
继承
通常,你将创建完全定制的异常过滤器来满足你的应用需求。但是,在某些用例中,你可能只想扩展内置的默认全局异常过滤器,并根据某些因素覆盖行为。
为了将异常处理委托给基本过滤器,你需要扩展 BaseExceptionFilter 并调用继承的 catch() 方法。
// all-exceptions.filter.ts
import { Catch, ArgumentsHost } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
super.catch(exception, host);
}
}提示
扩展 BaseExceptionFilter 的方法范围和控制器范围的过滤器不应使用 new 实例化。相反,让框架自动实例化它们
全局过滤器可以扩展基本过滤器。这可以通过两种方式之一完成
第一种方法是在实例化自定义全局过滤器时注入 HttpAdapter 引用
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();