Appearance
NestJS 异常处理 (Exception Handling)
NestJS 提供了一个内置的异常处理系统,它负责处理应用程序中抛出的异常并以适当的方式响应客户端。NestJS 的异常处理系统基于异常过滤器,它能够捕获异常并返回适当的错误响应。
基础概念
NestJS 的异常处理系统包括:
- 内置异常类 - 提供常见的HTTP异常
- 异常过滤器 - 捕获和处理异常
- 全局异常处理 - 处理未捕获的异常
内置异常类
NestJS 提供了多个内置异常类,它们都继承自 HttpException:
1. HttpException
基础异常类,可以自定义HTTP状态码和消息:
typescript
import { HttpException, HttpStatus } from '@nestjs/common';
@Post()
create() {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
2. 常见HTTP异常
typescript
// 400 Bad Request
throw new BadRequestException('Invalid input');
// 401 Unauthorized
throw new UnauthorizedException();
// 403 Forbidden
throw new ForbiddenException();
// 404 Not Found
throw new NotFoundException();
// 409 Conflict
throw new ConflictException();
// 500 Internal Server Error
throw new InternalServerErrorException();
// 502 Bad Gateway
throw new BadGatewayException();
// 503 Service Unavailable
throw new ServiceUnavailableException();
自定义异常
创建自定义异常类:
typescript
import { HttpException, HttpStatus } from '@nestjs/common';
export class CustomException extends HttpException {
constructor() {
super('Custom error message', HttpStatus.BAD_REQUEST);
}
}
// 或者使用字符串消息
export class BusinessLogicException extends HttpException {
constructor(error: string, description?: string) {
const response = {
error,
message: description,
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
};
super(response, HttpStatus.UNPROCESSABLE_ENTITY);
}
}
异常过滤器
创建自定义异常过滤器:
typescript
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,
message: exception.message,
});
}
}
使用异常过滤器
1. 在控制器中使用
typescript
import { Controller, UseFilters } from '@nestjs/common';
import { HttpExceptionFilter } from './http-exception.filter';
@Controller('cats')
@UseFilters(HttpExceptionFilter)
export class CatsController {
@Get()
findAll() {
throw new BadRequestException('Something went wrong');
}
}
2. 在方法级别使用
typescript
@Get(':id')
@UseFilters(HttpExceptionFilter)
findOne(@Param('id') id: string) {
if (id === '0') {
throw new NotFoundException('Cat not found');
}
return this.catsService.findOne(id);
}
3. 全局注册
typescript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './common/exception-filters/http-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
捕获多种异常
异常过滤器可以捕获多种异常类型:
typescript
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
import { Response } from 'express';
import { HttpException, BadRequestException, UnauthorizedException } from '@nestjs/common';
@Catch(BadRequestException, UnauthorizedException)
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest();
const status = exception instanceof HttpException
? exception.getStatus()
: 500;
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.message || 'Internal server error',
});
}
}
全局异常处理
创建一个处理所有异常的过滤器:
typescript
import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus } from '@nestjs/common';
import { Response } from 'express';
import { Logger } from 'winston';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private logger: Logger) {}
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest();
// 记录异常信息
this.logger.error(exception.message, exception.stack);
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.message,
});
}
}
错误日志记录
创建一个带日志记录的异常过滤器:
typescript
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, Logger } from '@nestjs/common';
import { Response } from 'express';
@Catch()
export class LoggingExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(LoggingExceptionFilter.name);
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest();
const status = exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
// 记录错误日志
this.logger.error(
`Exception: ${exception.message}`,
exception.stack,
`Path: ${request.url}`,
`Method: ${request.method}`,
);
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.message,
});
}
}
业务逻辑异常
创建特定于业务逻辑的异常过滤器:
typescript
import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus } from '@nestjs/common';
import { Response } from 'express';
import { BusinessLogicException } from './business-logic.exception';
@Catch(BusinessLogicException)
export class BusinessLogicExceptionFilter implements ExceptionFilter {
catch(exception: BusinessLogicException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest();
response.status(HttpStatus.UNPROCESSABLE_ENTITY).json({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
timestamp: new Date().toISOString(),
path: request.url,
error: exception.getResponse(),
});
}
}
异步异常处理
处理异步操作中的异常:
typescript
@Post()
async create(@Body() createCatDto: CreateCatDto) {
try {
return await this.catsService.create(createCatDto);
} catch (error) {
if (error instanceof ValidationException) {
throw new BadRequestException('Validation failed');
}
throw new InternalServerErrorException('Something went wrong');
}
}
管道与异常处理
管道验证失败时会抛出 BadRequestException:
// DTO 验证
import { IsString, IsInt, Min } from 'class-validator';
export class CreateCatDto {
@IsString()
name: string;
@IsInt()
@Min(0)
age: number;
}
// 控制器
@Post()
async create(@Body() createCatDto: CreateCatDto) {
// 如果验证失败,会自动抛出 BadRequestException
return this.catsService.create(createCatDto);
}
异常过滤器与依赖注入
异常过滤器可以使用依赖注入:
typescript
import { ExceptionFilter, Catch, ArgumentsHost, Inject } from '@nestjs/common';
import { Response } from 'express';
import { Logger } from 'winston';
@Catch(HttpException)
export class InjectedExceptionFilter implements ExceptionFilter {
constructor(@Inject('winston') private readonly logger: Logger) {}
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest();
// 使用注入的服务
this.logger.error(
`Exception occurred: ${exception.message}`,
{
path: request.url,
method: request.method,
statusCode: exception.getStatus(),
},
);
response.status(exception.getStatus()).json({
statusCode: exception.getStatus(),
timestamp: new Date().toISOString(),
path: request.url,
message: exception.message,
});
}
}
异常处理最佳实践
1. 统一错误响应格式
typescript
// error-response.interface.ts
export interface ErrorResponse {
statusCode: number;
timestamp: string;
path: string;
message: string;
error?: string;
}
// 统一异常过滤器
@Catch()
export class UnifiedExceptionFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost): any {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest();
const errorResponse: ErrorResponse = {
statusCode: exception.getStatus?.() || 500,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.message || 'Internal server error',
};
response.status(errorResponse.statusCode).json(errorResponse);
}
}
2. 分级异常处理
typescript
// 创建不同级别的异常过滤器
@Catch(ValidationException)
export class ValidationExceptionFilter implements ExceptionFilter {
catch(exception: ValidationException, host: ArgumentsHost) {
// 专门处理验证异常
}
}
@Catch(BusinessException)
export class BusinessExceptionFilter implements ExceptionFilter {
catch(exception: BusinessException, host: ArgumentsHost) {
// 专门处理业务异常
}
}
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
// 处理其他所有异常
}
}
3. 异常处理中间件
结合中间件和异常过滤器:
typescript
// 错误处理中间件
export function errorHandlingMiddleware(err: any, req: any, res: any, next: any) {
if (err) {
// 将错误传递给 NestJS 的异常处理系统
next(err);
} else {
next();
}
}
与守卫、拦截器的关系
异常处理在请求处理流程中的位置:
- 中间件 - 最先执行
- 守卫 - 权限控制
- 拦截器 - 请求/响应转换
- 管道 - 数据验证和转换
- 控制器方法 - 业务逻辑
- 异常过滤器 - 异常处理
异常处理策略
1. 开发环境 vs 生产环境
typescript
@Catch()
export class EnvironmentAwareExceptionFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest();
const status = exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const isDevelopment = process.env.NODE_ENV === 'development';
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.message,
stack: isDevelopment ? exception.stack : undefined, // 生产环境不暴露堆栈信息
});
}
}
2. 异常统计
typescript
@Injectable()
export class ExceptionStatisticsFilter implements ExceptionFilter {
private statistics = new Map<string, number>();
catch(exception: any, host: ArgumentsHost) {
const errorType = exception.constructor.name;
const currentCount = this.statistics.get(errorType) || 0;
this.statistics.set(errorType, currentCount + 1);
// 记录统计信息
console.log('Exception Statistics:', Object.fromEntries(this.statistics));
// 调用默认异常处理
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest();
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.message,
});
}
}
总结
NestJS 的异常处理系统提供了强大而灵活的机制来处理应用程序中的错误。通过内置异常类、自定义异常和异常过滤器,你可以创建健壮的错误处理策略。
异常处理的主要特点:
- 提供丰富的内置异常类
- 支持自定义异常和过滤器
- 可以在不同级别应用异常处理
- 支持依赖注入
- 与 NestJS 的其他功能集成良好
- 允许统一的错误响应格式