Skip to content
On this page

NestJS 异常处理 (Exception Handling)

NestJS 提供了一个内置的异常处理系统,它负责处理应用程序中抛出的异常并以适当的方式响应客户端。NestJS 的异常处理系统基于异常过滤器,它能够捕获异常并返回适当的错误响应。

基础概念

NestJS 的异常处理系统包括:

  1. 内置异常类 - 提供常见的HTTP异常
  2. 异常过滤器 - 捕获和处理异常
  3. 全局异常处理 - 处理未捕获的异常

内置异常类

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. 中间件 - 最先执行
  2. 守卫 - 权限控制
  3. 拦截器 - 请求/响应转换
  4. 管道 - 数据验证和转换
  5. 控制器方法 - 业务逻辑
  6. 异常过滤器 - 异常处理

异常处理策略

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 的其他功能集成良好
  • 允许统一的错误响应格式