深入理解 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) // 只捕获 HttpException
export 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 文档 - 日志系统详解
- 本文链接:https://fridolph.top/posts/2025-11-29__nest13-filters
- 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 许可协议。