NestJS异常处理 #

异常处理概述 #

NestJS内置了异常处理层,负责捕获应用中未处理的异常,并返回适当的响应给用户。

内置异常 #

HttpException #

typescript
import { Controller, Get, HttpException, HttpStatus } from '@nestjs/common';

@Controller('users')
export class UsersController {
  @Get()
  findAll() {
    throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
  }
}

响应:

json
{
  "statusCode": 403,
  "message": "Forbidden"
}

常用内置异常 #

异常类 状态码 说明
BadRequestException 400 错误请求
UnauthorizedException 401 未授权
NotFoundException 404 未找到
ForbiddenException 403 禁止访问
NotAcceptableException 406 不可接受
RequestTimeoutException 408 请求超时
ConflictException 409 冲突
GoneException 410 已不存在
PayloadTooLargeException 413 负载过大
UnsupportedMediaTypeException 415 不支持的媒体类型
UnprocessableEntityException 422 无法处理的实体
InternalServerErrorException 500 内部服务器错误
NotImplementedException 501 未实现
BadGatewayException 502 错误网关
ServiceUnavailableException 503 服务不可用

使用内置异常 #

typescript
import {
  Controller,
  Get,
  NotFoundException,
  UnauthorizedException,
  BadRequestException,
} from '@nestjs/common';

@Controller('users')
export class UsersController {
  @Get(':id')
  findOne(@Param('id') id: string) {
    if (!id) {
      throw new BadRequestException('ID is required');
    }

    const user = this.usersService.findOne(id);
    if (!user) {
      throw new NotFoundException(`User ${id} not found`);
    }

    return user;
  }
}

自定义响应 #

typescript
@Get()
findAll() {
  throw new HttpException(
    {
      status: HttpStatus.FORBIDDEN,
      error: 'This is a custom message',
      details: { reason: 'Access denied' },
    },
    HttpStatus.FORBIDDEN,
  );
}

响应:

json
{
  "status": 403,
  "error": "This is a custom message",
  "details": {
    "reason": "Access denied"
  }
}

自定义异常 #

创建自定义异常 #

typescript
import { HttpException, HttpStatus } from '@nestjs/common';

export class UserNotFoundException extends HttpException {
  constructor(id: string) {
    super(`User with ID ${id} not found`, HttpStatus.NOT_FOUND);
  }
}

export class UserAlreadyExistsException extends HttpException {
  constructor(email: string) {
    super(
      {
        status: HttpStatus.CONFLICT,
        error: 'User already exists',
        email,
      },
      HttpStatus.CONFLICT,
    );
  }
}

使用自定义异常 #

typescript
@Controller('users')
export class UsersController {
  @Get(':id')
  findOne(@Param('id') id: string) {
    const user = this.usersService.findOne(id);
    if (!user) {
      throw new UserNotFoundException(id);
    }
    return user;
  }
}

异常过滤器 #

创建异常过滤器 #

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();
    const exceptionResponse = exception.getResponse();

    response.status(status).json({
      code: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message:
        typeof exceptionResponse === 'string'
          ? exceptionResponse
          : (exceptionResponse as any).message,
    });
  }
}

注册过滤器 #

方法级别 #

typescript
@Post()
@UseFilters(new HttpExceptionFilter())
create(@Body() createUserDto: CreateUserDto) {
  return this.usersService.create(createUserDto);
}

控制器级别 #

typescript
@Controller('users')
@UseFilters(new HttpExceptionFilter())
export class UsersController {}

全局级别 #

typescript
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}

捕获所有异常 #

typescript
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
} from '@nestjs/common';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const message =
      exception instanceof HttpException
        ? exception.getResponse()
        : 'Internal server error';

    response.status(status).json({
      code: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message,
    });
  }
}

捕获特定异常 #

typescript
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
import { QueryFailedError } from 'typeorm';

@Catch(QueryFailedError)
export class DatabaseExceptionFilter implements ExceptionFilter {
  catch(exception: QueryFailedError, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();

    response.status(500).json({
      code: 500,
      message: 'Database error',
      detail: exception.message,
    });
  }
}

继承BaseExceptionFilter #

typescript
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
} from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';

@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    // 自定义逻辑
    console.error('Exception caught:', exception);

    // 调用父类方法
    super.catch(exception, host);
  }
}

异常处理最佳实践 #

1. 统一异常响应格式 #

typescript
export class BusinessException extends HttpException {
  constructor(
    message: string,
    private readonly errorCode: string,
    status: HttpStatus = HttpStatus.BAD_REQUEST,
  ) {
    super({ message, errorCode }, status);
  }

  getErrorCode(): string {
    return this.errorCode;
  }
}

@Catch(BusinessException)
export class BusinessExceptionFilter implements ExceptionFilter {
  catch(exception: BusinessException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();

    response.status(exception.getStatus()).json({
      success: false,
      code: exception.getStatus(),
      errorCode: exception.getErrorCode(),
      message: exception.message,
      timestamp: new Date().toISOString(),
    });
  }
}

2. 错误码定义 #

typescript
export enum ErrorCode {
  USER_NOT_FOUND = 'USER_NOT_FOUND',
  USER_ALREADY_EXISTS = 'USER_ALREADY_EXISTS',
  INVALID_PASSWORD = 'INVALID_PASSWORD',
  UNAUTHORIZED = 'UNAUTHORIZED',
  FORBIDDEN = 'FORBIDDEN',
}

export class UserNotFoundException extends BusinessException {
  constructor(id: string) {
    super(
      `User with ID ${id} not found`,
      ErrorCode.USER_NOT_FOUND,
      HttpStatus.NOT_FOUND,
    );
  }
}

3. 服务层异常 #

typescript
@Injectable()
export class UsersService {
  async findOne(id: string): Promise<User> {
    const user = await this.usersRepository.findOne({ where: { id } });
    
    if (!user) {
      throw new UserNotFoundException(id);
    }
    
    return user;
  }
}

4. 验证异常处理 #

typescript
import { ArgumentsHost, Catch, ExceptionFilter, HttpStatus } from '@nestjs/common';
import { ValidationError } from 'class-validator';

@Catch(ValidationError)
export class ValidationExceptionFilter implements ExceptionFilter {
  catch(exception: ValidationError[], host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();

    const errors = exception.map(error => ({
      property: error.property,
      constraints: error.constraints,
    }));

    response.status(HttpStatus.BAD_REQUEST).json({
      code: HttpStatus.BAD_REQUEST,
      message: 'Validation failed',
      errors,
    });
  }
}

异常日志 #

typescript
import { ExceptionFilter, Catch, ArgumentsHost, Logger } from '@nestjs/common';

@Catch()
export class LoggingExceptionFilter implements ExceptionFilter {
  private readonly logger = new Logger(LoggingExceptionFilter.name);

  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const request = ctx.getRequest();
    const response = ctx.getResponse();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    this.logger.error(
      `Exception caught: ${request.method} ${request.url}`,
      exception instanceof Error ? exception.stack : undefined,
    );

    response.status(status).json({
      code: status,
      message: 'An error occurred',
      timestamp: new Date().toISOString(),
    });
  }
}

总结 #

本章学习了NestJS异常处理:

  • 内置HTTP异常
  • 自定义异常
  • 异常过滤器的创建和使用
  • 全局异常处理
  • 异常处理最佳实践

接下来,让我们学习 中间件

最后更新:2026-03-28