异常处理
异常过滤器统一错误响应格式。见 pipes 抛出的 BadRequestException 等。
NestJS 提供了强大的异常处理层。应用在运行期间抛出的异常(无论是主动抛出还是意外错误)都会被统一的异常层捕获,并转换成标准的 HTTP 响应。开发者可以通过内置异常类、自定义异常以及异常过滤器来定制错误响应逻辑。
一、内置异常类
NestJS 在 @nestjs/common 中提供了一系列继承自 HttpException 的内置异常类,覆盖了常见的 HTTP 错误状态码。使用这些异常可以使代码更具可读性。
常用内置异常类
| 异常类 | HTTP 状态码 | 描述 |
|---|---|---|
BadRequestException | 400 | 请求参数错误(如验证失败) |
UnauthorizedException | 401 | 未认证(缺少有效凭证) |
ForbiddenException | 403 | 无权限访问资源 |
NotFoundException | 404 | 资源未找到 |
ConflictException | 409 | 请求与当前资源状态冲突 |
InternalServerErrorException | 500 | 服务器内部错误(默认) |
NotAcceptableException | 406 | 无法根据请求 Accept 头生成响应 |
RequestTimeoutException | 408 | 请求超时 |
MethodNotAllowedException | 405 | HTTP 方法不允许 |
UnsupportedMediaTypeException | 415 | 不支持的媒体类型 |
注意:
@nestjs/common没有内置TooManyRequestsException。429(请求过于频繁)需用new HttpException('Too Many Requests', HttpStatus.TOO_MANY_REQUESTS)自行抛出,或直接使用@nestjs/throttler提供的限流守卫(其内部抛出ThrottlerException)。
使用示例:
@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 是所有异常类的基类。你也可以直接使用它,传入状态码和消息:
throw new HttpException("自定义错误", HttpStatus.FORBIDDEN);
HttpStatus 枚举可从 @nestjs/common 导入。
二、自定义异常
如果内置异常不满足需求,可以创建自己的异常类。通常有两种方式:
1. 直接扩展 HttpException
import { HttpException, HttpStatus } from "@nestjs/common";
export class BusinessException extends HttpException {
constructor(message: string, status: HttpStatus = HttpStatus.BAD_REQUEST) {
super(message, status);
}
}
2. 扩展内置异常类
import { BadRequestException } from "@nestjs/common";
export class ValidationFailedException extends BadRequestException {
constructor(errorDetails: any) {
super({
message: "Validation failed",
errors: errorDetails,
});
}
}
自定义响应体:HttpException 构造函数可以接收对象作为第一个参数,从而自定义返回的 JSON 结构。
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 提供了访问底层请求/响应对象的能力。
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()) | 整个应用 |
方法/控制器级示例:
@Controller("users")
@UseFilters(HttpExceptionFilter) // 控制器级
export class UsersController {
@Get(":id")
@UseFilters(AnotherFilter) // 方法级(覆盖控制器级?实际会组合,多个过滤器顺序执行)
findOne() {}
}
全局过滤器注册:
// 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 令牌),让框架进行依赖注入。
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() 中的参数:
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
// 处理日志、统一兜底响应
}
}
四、内置全局异常过滤器
NestJS 内置了一个全局异常过滤器,当请求到达异常处理层而未注册任何自定义过滤器时,该内置过滤器会接管。
- 默认行为:
- 如果异常是
HttpException或其子类,则返回对应的statusCode和message。 - 如果是其他错误(如
TypeError,Error),则返回500 Internal Server Error,并隐藏内部错误细节(在生产环境)。
- 如果异常是
- 结构:默认返回格式为
{ statusCode: number, message: string, error?: string }。
你不需要主动引入内置过滤器,它是框架的一部分。但在需要统一错误结构时,通常推荐自定义全局过滤器。
五、最佳实践
- 优先使用内置异常类:它们已经涵盖了绝大部分业务场景,使用它们可使代码意图清晰。
- 自定义业务异常:对于特定业务规则错误,扩展
HttpException并携带错误码或附加信息。 - 全局异常过滤器:用于统一响应格式、记录错误日志、接入监控系统(Sentry/DataDog)。
- 区分可恢复错误与崩溃:不要捕获不该捕获的低级异常(如
Error),让它们触发全局过滤器并记录堆栈。 - 使用
class-validator管道验证:验证失败自动抛出BadRequestException,无需手动处理。
总结
| 概念 | 作用 |
|---|---|
HttpException 基类 | 所有异常的根,可设置状态码和消息 |
| 内置异常类 | 快速生成常见 HTTP 错误响应 |
| 自定义异常 | 封装特定业务错误或附加信息 |
@Catch() 过滤器 | 捕获指定异常,自定义响应逻辑 |
@UseFilters() | 方法/控制器级应用过滤器 |
app.useGlobalFilters / APP_FILTER | 全局过滤器,统一错误处理 |
| 内置全局过滤器 | 默认的兜底异常处理,生产环境不暴露内部错误 |
通过合理使用异常过滤器,你可以实现对错误响应的完全控制,同时保持代码整洁。
参考文献
以下链接在编写时均可正常访问:
| 资料 | 说明 |
|---|---|
| NestJS 文档 | 官方 |
| Exception filters | 本章主题 |
| Request lifecycle | 执行顺序 |
相关文章
认证与授权
认证(Authentication):确认「你是谁」(如 JWT、Session)。 - 授权(Authorization):确认「你能做什么」(如 RBAC、策略检查)。
缓存
@nestjs/cache-manager 统一缓存 API,存储实现可插拔。
提供者与服务
Service 是最常见的 Provider,封装业务逻辑。见 module。
守卫 (Guards) 与授权
守卫决定是否放行请求,常用于认证与授权。见 auth。
配置管理(Config 模块)
@nestjs/config 加载 .env 并提供 ConfigService。见 工程化 env。
数据库集成(以 TypeORM 为例)
Nest 通过 @nestjs/typeorm 等包集成 ORM;生产环境用 migration,慎用 synchronize。亦可选用 Prisma、MikroORM 等(见 官方 Database)。
Series
new
10 / 19