什么是异常过滤器?🤔
异常过滤器(Exception Filter)是 NestJS 中处理异常的机制,它可以捕获整个应用程序中抛出的异常,并处理这些异常。通过异常过滤器,我们可以集中处理错误,统一错误响应格式,并进行适当的错误日志记录。
Express vs NestJS 对比 🆚
让我们看看在传统 Express 和 NestJS 中如何处理异常:
Express 写法:
javascript
// 错误处理中间件
app.use((err, req, res, next) => {
console.error(err.stack);
// 处理不同类型的错误
if (err instanceof ValidationError) {
return res.status(400).json({
code: 400,
message: '验证错误',
errors: err.errors
});
}
if (err.name === 'UnauthorizedError') {
return res.status(401).json({
code: 401,
message: '未授权'
});
}
// 默认错误响应
res.status(500).json({
code: 500,
message: '服务器内部错误'
});
});NestJS 写法:
typescript
@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;
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception instanceof HttpException
? exception.message
: '服务器内部错误'
});
}
}内置异常 🎯
NestJS 提供了一系列内置的 HTTP 异常类:
typescript
// 400 Bad Request
throw new BadRequestException('参数验证失败');
// 401 Unauthorized
throw new UnauthorizedException('未授权');
// 403 Forbidden
throw new ForbiddenException('权限不足');
// 404 Not Found
throw new NotFoundException('资源不存在');
// 429 Too Many Requests
throw new ThrottlerException('请求过于频繁');
// 500 Internal Server Error
throw new InternalServerErrorException('服务器错误');自定义异常过滤器 🛠️
1. 基本异常过滤器
typescript
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
constructor(private readonly logger: Logger) {}
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
const errorResponse = {
code: status,
timestamp: new Date().toISOString(),
path: request.url,
method: request.method,
message: exception.message || null,
};
this.logger.error(
`${request.method} ${request.url}`,
exception.stack,
'ExceptionFilter'
);
response
.status(status)
.json(errorResponse);
}
}2. 业务异常过滤器
typescript
// exceptions/business.exception.ts
export class BusinessException extends HttpException {
constructor(
public readonly code: string,
message: string,
status: HttpStatus = HttpStatus.BAD_REQUEST
) {
super({ code, message }, status);
}
}
// filters/business-exception.filter.ts
@Catch(BusinessException)
export class BusinessExceptionFilter implements ExceptionFilter {
catch(exception: BusinessException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
response.status(exception.getStatus()).json({
code: exception.code,
message: exception.message,
timestamp: new Date().toISOString()
});
}
}3. 数据库异常过滤器
typescript
@Catch(TypeORMError)
export class DatabaseExceptionFilter implements ExceptionFilter {
constructor(private readonly logger: Logger) {}
catch(exception: TypeORMError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
let status = HttpStatus.INTERNAL_SERVER_ERROR;
let message = '数据库错误';
// 处理不同类型的数据库错误
if (exception instanceof QueryFailedError) {
if (exception.message.includes('duplicate key')) {
status = HttpStatus.CONFLICT;
message = '数据已存在';
}
}
this.logger.error(
`Database error: ${exception.message}`,
exception.stack,
'DatabaseExceptionFilter'
);
response.status(status).json({
statusCode: status,
message,
timestamp: new Date().toISOString(),
path: request.url
});
}
}高级用法 🚀
1. 全局异常过滤器
typescript
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const logger = new Logger();
app.useGlobalFilters(
new HttpExceptionFilter(logger),
new BusinessExceptionFilter(),
new DatabaseExceptionFilter(logger)
);
await app.listen(3000);
}2. 带有依赖注入的异常过滤器
typescript
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
constructor(
private readonly httpAdapterHost: HttpAdapterHost,
private readonly logger: Logger,
private readonly configService: ConfigService
) {}
catch(exception: unknown, host: ArgumentsHost) {
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const request = ctx.getRequest();
const httpStatus =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const responseBody = {
statusCode: httpStatus,
timestamp: new Date().toISOString(),
path: httpAdapter.getRequestUrl(request),
message: this.getExceptionMessage(exception),
stack: this.shouldIncludeStack(exception)
? this.getExceptionStack(exception)
: undefined
};
this.logException(exception, request);
httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
}
private getExceptionMessage(exception: unknown): string {
if (exception instanceof HttpException) {
return exception.message;
}
return '服务器内部错误';
}
private getExceptionStack(exception: unknown): string | undefined {
if (exception instanceof Error) {
return exception.stack;
}
return undefined;
}
private shouldIncludeStack(exception: unknown): boolean {
return this.configService.get('NODE_ENV') === 'development';
}
private logException(exception: unknown, request: Request): void {
const message = this.getExceptionMessage(exception);
const stack = this.getExceptionStack(exception);
this.logger.error(
`Exception occurred in ${request.method} ${request.url}: ${message}`,
stack,
'AllExceptionsFilter'
);
}
}实战示例 💡
1. API 错误处理系统
typescript
// interfaces/api-error.interface.ts
export interface ApiError {
code: string;
message: string;
details?: any;
}
// exceptions/api.exception.ts
export class ApiException extends HttpException {
constructor(error: ApiError, status: HttpStatus = HttpStatus.BAD_REQUEST) {
super(error, status);
}
}
// filters/api-exception.filter.ts
@Catch(ApiException)
export class ApiExceptionFilter implements ExceptionFilter {
constructor(private readonly logger: Logger) {}
catch(exception: ApiException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
const error = exception.getResponse() as ApiError;
const errorResponse = {
...error,
timestamp: new Date().toISOString(),
path: request.url,
method: request.method
};
this.logger.error(
`API Error: ${error.message}`,
error.details,
'ApiExceptionFilter'
);
response.status(status).json(errorResponse);
}
}最佳实践 ✨
1. 错误代码管理
typescript
// constants/error-codes.ts
export const ErrorCodes = {
VALIDATION_ERROR: 'E0001',
UNAUTHORIZED: 'E0002',
FORBIDDEN: 'E0003',
NOT_FOUND: 'E0004',
INTERNAL_ERROR: 'E0005',
// ... 更多错误代码
} as const;
// 使用错误代码
throw new ApiException({
code: ErrorCodes.VALIDATION_ERROR,
message: '验证失败',
details: validationErrors
});2. 错误响应格式统一
typescript
interface StandardError {
code: string;
message: string;
details?: any;
timestamp: string;
path: string;
requestId?: string;
}
@Catch()
export class StandardExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost): void {
// ... 实现标准错误响应格式
}
}总结 📝
通过这篇文章,我们深入学习了:
- 异常过滤器的基本概念和作用
- NestJS 内置的异常类型
- 如何创建和使用自定义异常过滤器
- 异常处理的最佳实践
- 实际项目中的错误处理系统
异常过滤器是 NestJS 中处理错误的强大工具,它可以帮助我们:
- 统一处理异常
- 标准化错误响应
- 集中化错误日志
- 提供更好的错误追踪
- 改善应用程序的可维护性
下一篇文章,我们将探讨 NestJS 的配置系统(Configuration)。