
在nestjs中,Exception Filter(异常过滤器)是用于处理全局异常的一种机制。它可以捕获应用程序中发生的异常,并对其进行统一处理。本章我们来学习自定义Exception Filter。
首先 先创建一个新的项目:
nest new exception-filter

启动项目:
npm run start:dev

在controller 里抛个异常,修改app.controller.ts:
import { Controller, Get, HttpException, HttpStatus } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) { }
  @Get()
  getHello(): string {
    throw new HttpException('xxxxx', HttpStatus.BAD_REQUEST)
    return this.appService.getHello();
  }
}
HttpStatus 其实就是内置的一些状态码
此时 游览器访问 http://localhost:3000/ 可以看到返回的响应是400
以上返回的响应格式是内置 Exception Filter 生成
同时 也可以直接抛出具体的异常
import { BadRequestException, Controller, Get, HttpException, HttpStatus } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) { }
  @Get()
  getHello(): string {
    // throw new HttpException('xxxxx', HttpStatus.BAD_REQUEST)
    throw new BadRequestException('异常');
    return this.appService.getHello();
  }
}
继续游览器访问 http://localhost:3000/ 
接着我们定义一个 exception filter:
nest g filter test --flat --no-spec

修改 test.filter.ts:
import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter } from '@nestjs/common';
@Catch(BadRequestException)
export class TestFilter implements ExceptionFilter {
  catch(exception: BadRequestException, host: ArgumentsHost) {
    const http = host.switchToHttp()
    const response = http.getResponse()
    const statusCode = exception.getStatus()
    response.status(statusCode).json({
      code: statusCode,
      message: exception.message,
      error: 'Bad Request',
      xxx: 111
    })
  }
}
在AppModule 里全局引入:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { TestFilter } from './test.filter';
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new TestFilter())
  await app.listen(3000);
}
bootstrap();
访问 http://localhost:3000/ 可以看到响应是自定义的
但是目前只是@Catch 了 BadRequestException的异常 如果抛出的是其他异常 那么依然是原来的格式
例如使用BadGatewayException 格式
import { BadGatewayException, BadRequestException, Controller, Get, HttpException, HttpStatus } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) { }
  @Get()
  getHello(): string {
    // throw new HttpException('xxxxx', HttpStatus.BAD_REQUEST)
    throw new BadGatewayException('异常');
    return this.appService.getHello();
  }
}
再访问 http://localhost:3000/ 可以看到依然是默认格式
接下来我们看一下BadRequestException 和 BadGatewayException 都是哪里来的

可以看到 BadRequestException 和 BadGatewayException 都是 HttpException 的子类
那么我们在异常过滤那里将 @Catch 指定 HttpException 即可包含所有
@Catch(HttpException)
export class TestFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const http = host.switchToHttp()
    const response = http.getResponse()
    const statusCode = exception.getStatus()
    response.status(statusCode).json({
      code: statusCode,
      message: exception.message,
      error: 'Bad Request',
      xxx: 111
    })
  }
}
再次访问 http://localhost:3000/ 可以看到现在的格式是自定义的格式
自此HttpException 都会被处理,但是当我们用 ValidationPipe 也有问题例如:
创建 www.dto.ts
export class WwwDto {
    aaa: string;
    bbb: number;
}
创建接口
  @Post('www')
  www(@Body() www: WwwDto) {
    return www
  }
安装需要用到的包:
npm install --save class-validator class-transformer
接着修改WwwDto 添加校验规则:
import { IsEmail, IsNotEmpty, IsNumber } from "class-validator";
export class WwwDto {
    @IsNotEmpty({ message: 'aaa 不能为空' })
    @IsEmail({}, { message: 'aaa 不是邮箱格式' })
    aaa: string;
    @IsNumber({}, { message: 'bbb 不是数字' })
    @IsNotEmpty({ message: 'bbb 不能为空' })
    bbb: number;
}
接着全局使用ValidationPipe:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { TestFilter } from './test.filter';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new ValidationPipe() as any)
  app.useGlobalFilters(new TestFilter())
  await app.listen(3000);
}
bootstrap();
最后在postman 里测试一下:
可以看到提示的错误不对, exception filter 会拦截所有 HttpException,但是没有对这种情况做支持。
我们再次把  app.useGlobalFilters(new TestFilter()) 注释掉
访问
接着我们修改 test.filter.ts 实现对ValidationPipe的支持
import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter, HttpException } from '@nestjs/common';
@Catch(HttpException)
export class TestFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const http = host.switchToHttp()
    const response = http.getResponse()
    const statusCode = exception.getStatus()
    const res = exception.getResponse() as { message: string[] };
    response.status(statusCode).json({
      code: statusCode,
      message: res?.message?.join ? res?.message?.join(',') : exception.message,
      error: 'Bad Request',
      xxx: 111
    })
  }
}
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { TestFilter } from './test.filter';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe())
  app.useGlobalFilters(new TestFilter())
  await app.listen(3000);
}
bootstrap();

自此ValidationPipe 的错误和其他的错误就都返回了正确的格式
如果想在 Filter 里注入 AppService 则需要修改注册方式:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { TestFilter } from './test.filter';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe())
 // app.useGlobalFilters(new TestFilter())
  await app.listen(3000);
}
bootstrap();
接着在 AppModule 里注册一个 token 为 APP_FILTER 的 provider:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { APP_FILTER } from '@nestjs/core';
import { TestFilter } from './test.filter';
@Module({
  imports: [],
  controllers: [AppController],
  providers: [
    AppService,
    {
      provide: APP_FILTER,
      useClass: TestFilter
    }
  ],
})
export class AppModule { }
Nest 会把所有 token 为 APP_FILTER 的 provider 注册为全局 Exception Filter
接着就可以在TestFilter 注入 AppService
import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter, HttpException, Inject } from '@nestjs/common';
import { AppService } from './app.service';
@Catch(HttpException)
export class TestFilter implements ExceptionFilter {
  @Inject(AppService)
  private service: AppService
  catch(exception: HttpException, host: ArgumentsHost) {
    const http = host.switchToHttp()
    const response = http.getResponse()
    const statusCode = exception.getStatus()
    const res = exception.getResponse() as { message: string[] };
    response.status(statusCode).json({
      code: statusCode,
      message: res?.message?.join ? res?.message?.join(',') : exception.message,
      error: 'Bad Request',
      xxx: 111,
      www: this.service.getHello()
    })
  }
}
访问 http://localhost:3000/



















