【NestJS】13 异常过滤器(Exception Filter)
深入理解 NestJS 异常过滤器机制,掌握统一错误处理、自定义异常响应和错误日志记录,从基础异常处理到企业级错误管理的完整实践指南。
在开发过程中,错误是不可避免的。异常过滤器可以捕获和处理这些错误,返回友好的错误响应给客户端。这对于 API 开发非常重要,因为我们需要向客户端提供清晰、一致的错误信息 🛡️
一、什么是异常过滤器?
异常过滤器的定义 🎯
异常过滤器(Exception Filter) 是捕获和处理异常的类。它实现了 ExceptionFilter 接口,可以捕获特定类型的异常并返回自定义的响应。
异常过滤器的核心作用:
- 捕获应用中抛出的异常
- 转换异常为统一的响应格式
- 记录错误日志便于排查
- 向客户端返回友好的错误信息
📚 参考资源:
- NestJS Exception Filters 官方文档 - 官方完整指南
- HTTP 状态码规范 - MDN HTTP 状态码文档
异常过滤器的执行时机 📍
在 NestJS 的请求处理流程中,异常过滤器的位置如下:
请求处理 → 异常抛出 → 异常过滤器 → 响应返回当请求处理过程中的任何环节抛出异常时,异常过滤器会被自动调用。
异常过滤器的核心用途
异常过滤器在实际应用中主要用于四大场景:
- 统一错误格式:将所有错误转换为统一的 JSON 格式
- 记录错误日志:记录详细的错误信息,便于排查问题
- 友好错误提示:向客户端返回易于理解的错误信息
- 错误分类处理:根据不同类型的错误返回不同的响应
二、内置异常类
HttpException 基础异常类
HttpException 是 NestJS 的基础异常类,所有 HTTP 异常都继承自它。
基本用法:
throw new HttpException('用户不存在', HttpStatus.NOT_FOUND);这会返回 404 错误,响应体是:
{ "statusCode": 404, "message": "用户不存在", "error": "Not Found"}📚 参考资源:
- HTTP 状态码完整列表 - HTTP 状态码速查表
常用内置异常类
NestJS 提供了丰富的内置异常类,涵盖了常见的 HTTP 错误场景:
BadRequestException - 400 错误
throw new BadRequestException('参数错误');用于客户端请求参数不正确的场景。
UnauthorizedException - 401 错误
throw new UnauthorizedException('未授权,请先登录');用于用户未认证的场景。
ForbiddenException - 403 错误
throw new ForbiddenException('没有权限访问此资源');用于用户已认证但权限不足的场景。
NotFoundException - 404 错误
throw new NotFoundException('资源不存在');用于请求的资源不存在的场景。
ConflictException - 409 错误
throw new ConflictException('资源冲突,用户名已存在');用于资源冲突的场景,如重复创建。
InternalServerErrorException - 500 错误
throw new InternalServerErrorException('服务器内部错误');用于服务器内部错误的场景。
自定义异常响应
可以自定义异常的响应内容,提供更详细的错误信息:
throw new HttpException( { statusCode: 400, message: '参数验证失败', errors: ['用户名不能为空', '邮箱格式不正确'], }, HttpStatus.BAD_REQUEST,);三、创建异常过滤器
基本异常过滤器
创建一个基本的异常过滤器,捕获所有异常:
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus,} from '@nestjs/common';import { Request, Response } from 'express';
@Catch()export class AllExceptionsFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>();
const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
const message = exception instanceof HttpException ? exception.getResponse() : '服务器内部错误';
response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, message: message, }); }}@Catch() 装饰器详解
@Catch() 装饰器用于指定要捕获的异常类型:
捕获特定异常:
@Catch(HttpException) // 只捕获 HttpExceptionexport class HttpExceptionFilter implements ExceptionFilter { // ...}捕获所有异常:
@Catch() // 捕获所有异常export class AllExceptionsFilter implements ExceptionFilter { // ...}捕获多种异常:
@Catch(HttpException, ValidationException) // 捕获多种异常export class MultipleExceptionFilter implements ExceptionFilter { // ...}ArgumentsHost 详解
ArgumentsHost 提供了执行上下文的完整信息:
catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const request = ctx.getRequest(); // 获取请求对象 const response = ctx.getResponse(); // 获取响应对象
// 访问请求信息 console.log(request.method); // GET, POST, etc. console.log(request.url); // /user/123 console.log(request.headers); // 请求头}📚 参考资源:
- ArgumentsHost API 文档 - 执行上下文详解
四、全局异常处理
在模块中注册全局异常过滤器
在 app.module.ts 中注册全局异常过滤器:
import { APP_FILTER } from '@nestjs/core';import { AllExceptionsFilter } from './common/filters/all-exceptions.filter';
@Module({ providers: [ { provide: APP_FILTER, useClass: AllExceptionsFilter, }, ],})export class AppModule {}在 main.ts 中注册
也可以在 main.ts 中注册全局异常过滤器:
import { AllExceptionsFilter } from './common/filters/all-exceptions.filter';
app.useGlobalFilters(new AllExceptionsFilter());注册多个异常过滤器
可以注册多个异常过滤器,它们会按照注册顺序执行:
@Module({ providers: [ { provide: APP_FILTER, useClass: HttpExceptionFilter, }, { provide: APP_FILTER, useClass: AllExceptionsFilter, }, ],})export class AppModule {}五、实战:统一的错误处理系统
需求分析 📋
创建一个完整的异常处理系统,实现以下功能:
- 统一错误响应格式
- 记录详细的错误日志
- 处理不同类型的异常
- 提供友好的错误信息
- 区分开发环境和生产环境的错误信息
完整实现
第一步:创建统一异常过滤器
创建 src/common/filters/all-exceptions.filter.ts 文件:
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Logger,} from '@nestjs/common';import { Request, Response } from 'express';
@Catch()export class AllExceptionsFilter implements ExceptionFilter { private readonly logger = new Logger(AllExceptionsFilter.name);
catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>();
let status = HttpStatus.INTERNAL_SERVER_ERROR; let message = '服务器内部错误'; let error: any = null;
// 处理 HttpException if (exception instanceof HttpException) { status = exception.getStatus(); const exceptionResponse = exception.getResponse();
if (typeof exceptionResponse === 'string') { message = exceptionResponse; } else if (typeof exceptionResponse === 'object') { const responseObj = exceptionResponse as any; message = responseObj.message || '请求失败'; error = responseObj.error || null; } } // 处理其他异常 else if (exception instanceof Error) { message = exception.message || '服务器内部错误'; this.logger.error( `未处理的异常: ${exception.message}`, exception.stack, 'AllExceptionsFilter', ); }
// 记录错误日志 this.logger.error( `${request.method} ${request.url} - ${status} - ${message}`, );
// 返回统一格式的错误响应 const errorResponse = { code: status, message: Array.isArray(message) ? message[0] : message, data: null, timestamp: new Date().toISOString(), path: request.url, ...(error && { error }), };
response.status(status).json(errorResponse); }}第二步:注册全局异常过滤器
在 src/app.module.ts 中注册:
import { APP_FILTER } from '@nestjs/core';import { AllExceptionsFilter } from './common/filters/all-exceptions.filter';
@Module({ providers: [ { provide: APP_FILTER, useClass: AllExceptionsFilter, }, ],})export class AppModule {}测试验证 ✅
1. 测试 HttpException
在控制器中抛出异常:
@Get(':id')findOne(@Param('id', ParseIntPipe) id: number) { if (id > 100) { throw new NotFoundException(`用户 ID ${id} 不存在`); } return { id, name: '测试用户' };}⚠️ 注意:如果在
UserController顶部使用了 JWT 验证守卫,测试时需要先注释掉。
访问 GET /user/999,应该返回:
{ "code": 404, "message": "用户 ID 999 不存在", "data": null, "timestamp": "2024-01-01T10:00:00.000Z", "path": "/user/999"}2. 测试未处理的异常
在控制器中抛出未处理的异常:
@Get('error')testError() { throw new Error('这是一个测试错误');}访问 GET /user/error,应该返回:
{ "code": 500, "message": "这是一个测试错误", "data": null, "timestamp": "2024-01-01T10:00:00.000Z", "path": "/user/error"}六、特定异常过滤器
HTTP 异常过滤器
创建一个专门处理 HTTP 异常的过滤器:
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 exceptionResponse = exception.getResponse();
let message: string; let error: any;
if (typeof exceptionResponse === 'string') { message = exceptionResponse; error = null; } else if (typeof exceptionResponse === 'object') { const responseObj = exceptionResponse as any; message = responseObj.message || '请求失败'; error = responseObj.error || null; } else { message = '请求失败'; error = null; }
response.status(status).json({ code: status, message: Array.isArray(message) ? message[0] : message, data: null, timestamp: new Date().toISOString(), path: request.url, ...(error && { error }), }); }}验证异常过滤器
创建一个专门处理验证异常的过滤器:
import { ExceptionFilter, Catch, ArgumentsHost, BadRequestException,} from '@nestjs/common';import { Request, Response } from 'express';
@Catch(BadRequestException)export class ValidationExceptionFilter implements ExceptionFilter { catch(exception: BadRequestException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>(); const exceptionResponse = exception.getResponse() as any;
response.status(400).json({ code: 400, message: '数据验证失败', errors: Array.isArray(exceptionResponse.message) ? exceptionResponse.message : [exceptionResponse.message], data: null, timestamp: new Date().toISOString(), path: request.url, }); }}七、异常过滤器的应用范围
全局异常过滤器
在 app.module.ts 中使用 APP_FILTER 注册全局异常过滤器(前面已经讲过)。
控制器级别异常过滤器
使用 @UseFilters() 装饰器应用到控制器:
@Controller('user')@UseFilters(HttpExceptionFilter)export class UserController { // 所有方法都使用此过滤器}方法级别异常过滤器
使用 @UseFilters() 装饰器应用到方法:
@Post()@UseFilters(ValidationExceptionFilter)create(@Body() createUserDto: CreateUserDto) { // 只有这个方法使用此过滤器}多个异常过滤器
可以同时应用多个异常过滤器:
@UseFilters(HttpExceptionFilter, ValidationExceptionFilter)export class UserController { // 按顺序应用多个过滤器}八、错误日志记录
记录错误日志
在异常过滤器中记录详细的错误日志:
import { Logger } from '@nestjs/common';
@Catch()export class AllExceptionsFilter implements ExceptionFilter { private readonly logger = new Logger(AllExceptionsFilter.name);
catch(exception: unknown, host: ArgumentsHost) { // ...
// 记录错误日志 if (exception instanceof Error) { this.logger.error( `异常: ${exception.message}`, exception.stack, 'AllExceptionsFilter', ); }
// 记录请求信息 this.logger.error( `${request.method} ${request.url} - ${status} - ${message}`, );
// ... }}📚 参考资源:
- NestJS Logger 文档 - 日志系统详解
日志级别
根据错误的严重程度使用不同的日志级别:
if (status >= 500) { this.logger.error(/* 服务器错误 */);} else if (status >= 400) { this.logger.warn(/* 客户端错误 */);} else { this.logger.log(/* 正常日志 */);}九、异常过滤器的最佳实践
1. 使用全局异常过滤器 🌐
// ✅ 推荐:全局异常处理@Module({ providers: [ { provide: APP_FILTER, useClass: AllExceptionsFilter, }, ],})export class AppModule {}2. 提供友好的错误信息 💬
// ✅ 推荐:友好的错误信息throw new NotFoundException('用户不存在,请检查用户 ID 是否正确');
// ❌ 避免:模糊的错误信息throw new NotFoundException('Not found');3. 记录详细的错误日志 📝
// ✅ 推荐:记录详细日志this.logger.error( `${request.method} ${request.url}`, exception.stack, 'AllExceptionsFilter',);4. 分类处理异常 🎯
// ✅ 推荐:根据异常类型分类处理if (exception instanceof HttpException) { // 处理 HTTP 异常} else if (exception instanceof Error) { // 处理其他异常}5. 不要暴露敏感信息 🔒
// ✅ 推荐:生产环境隐藏详细错误const isDevelopment = process.env.NODE_ENV === 'development';
const errorResponse = { code: status, message: message, ...(isDevelopment && { stack: exception.stack }),};
// ❌ 避免:暴露敏感信息throw new Error(`数据库连接失败: ${dbPassword}`);6. 统一错误响应格式 📊
// ✅ 推荐:统一的错误响应格式interface ErrorResponse { code: number; message: string; data: null; timestamp: string; path: string; error?: any;}十、常见问题排查
问题 1:异常过滤器未执行
问题描述:异常过滤器没有执行,异常未被捕获。
可能原因:
- 异常过滤器未注册
- 使用了
@Res()装饰器 - 异常类型不匹配
解决方案:
// ✅ 确保异常过滤器已注册@Module({ providers: [ { provide: APP_FILTER, useClass: AllExceptionsFilter, }, ],})
// ✅ 避免使用 @Res() 装饰器// 使用 @Res() 会禁用异常过滤器
// ✅ 检查 @Catch() 装饰器@Catch() // 捕获所有异常@Catch(HttpException) // 只捕获 HttpException问题 2:错误响应格式不正确
问题描述:错误响应格式不符合预期。
可能原因:异常过滤器逻辑有误。
解决方案:检查异常过滤器的逻辑,确保正确处理不同类型的异常。
问题 3:错误日志未记录
问题描述:错误日志没有记录。
可能原因:未在异常过滤器中记录日志。
解决方案:
// ✅ 添加日志记录private readonly logger = new Logger(AllExceptionsFilter.name);
catch(exception: unknown, host: ArgumentsHost) { this.logger.error(`异常: ${exception}`);}总结 🎯
这节课我们系统学习了 NestJS 异常过滤器的核心概念和实战应用。
核心知识点 ✅
- ✅ 理解了异常过滤器的定义:捕获和处理异常,返回统一的错误响应
- ✅ 学习了内置异常类:
HttpException、BadRequestException、NotFoundException等 - ✅ 掌握了如何创建异常过滤器:实现
ExceptionFilter接口,使用@Catch()装饰器 - ✅ 学习了全局异常处理:在
app.module.ts或main.ts中注册 - ✅ 完成了实战练习:创建了统一的异常过滤器
- ✅ 学习了特定异常过滤器:HTTP 异常过滤器和验证异常过滤器
- ✅ 掌握了异常过滤器的应用范围:全局、控制器级别、方法级别
- ✅ 学习了错误日志记录:记录详细的错误日志
常用内置异常速查表 📊
| 异常类 | HTTP 状态码 | 使用场景 |
|---|---|---|
BadRequestException | 400 | 请求参数错误 |
UnauthorizedException | 401 | 用户未认证 |
ForbiddenException | 403 | 权限不足 |
NotFoundException | 404 | 资源不存在 |
ConflictException | 409 | 资源冲突 |
InternalServerErrorException | 500 | 服务器内部错误 |
延伸学习资源 📚
- NestJS 官方文档 - Exception Filters - 官方完整指南
- HTTP 状态码规范 - MDN HTTP 状态码文档
- HTTP 状态码速查表 - 快速查询工具
- NestJS Logger 文档 - 日志系统详解
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!