[Nest.js] Exception, ExceptionFilter

Woong·2022년 11월 24일
0

Nestjs

목록 보기
5/28

Exception

  • HttpException 과 그 하위 클래스는 기본적으로 그에 해당하는 JSON 응답을 자동 생성한다.
{
  "statusCode": 500,
  "message": "Internal server error"
}

Exception throw 하기

  • contoller 에 하드코딩해서 throw 하는 방법
    • HttpException 생성자 인자 2개는 각각 response, statusCode
      • response는 string 또는 object
      • (optional) 3번째 인자로 object를 추가할 수 있는데, 이는 응답으로 직렬화되지 않고 서버 내부용으로만 사용 (로깅 목적 등)
    • response 에는 statusCode, message 가 포함된 JSON 으로 응답
@Get()
async findAll() {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
{
  "statusCode": 403,
  "message": "Forbidden"
}

커스텀 Exception

  • HttpException 을 상속하여 커스텀 Exception 을 만들어 사용할 수 있음
export class ForbiddenException extends HttpException {
  constructor() {
    super('Forbidden', HttpStatus.FORBIDDEN);
  }
}

내장된 Exception 목록

  • @nestjs/common 에 내장된 Exception 목록
BadRequestException
UnauthorizedException
NotFoundException
ForbiddenException
NotAcceptableException
RequestTimeoutException
ConflictException
GoneException
HttpVersionNotSupportedException
PayloadTooLargeException
UnsupportedMediaTypeException
UnprocessableEntityException
InternalServerErrorException
NotImplementedException
ImATeapotException
MethodNotAllowedException
BadGatewayException
ServiceUnavailableException
GatewayTimeoutException
PreconditionFailedException
  • 모든 내장된 Exception 은 options 파라미터를 통해 에러 원인과 설명을 추가할 수 있다.
throw new BadRequestException('Something bad happened', { cause: new Error(), description: 'Some error description' })
{
  "message": "Something bad happened",
  "error": "Some error description",
  "statusCode": 400,
}

Exception Filters

  • build-in exception 을 사용해도 되지만, 더 세부적으로 정의하고자할 때 Exception filter 사용
    • 로깅을 하거나, 응답 객체를 변경하는 등
    • @Catch : 어떤 Exception 을 잡을지 설정.
      • 인자 없을 경우 모든 Exception 에 대해 적용
    • catch 메소드에서
      • host 파라미터를 통해 요청, 응답 접근
      • exception 을 통해 throw 된 Exception 상태, 메시지 등 접근
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 error = exception.getResponse();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
        error // error: error 쓰는 대신 key-value 동일한 경우 생략 가능
      });
  }
}
  • method 레벨, Controller 레벨, global 레벨에서 Exception filter 를 적용할 수 있다
method 레벨 Exception filter 적용
  • method 에서는 @UseFilters 데코레이터를 통해 적용할 filter 를 설정한다.
    @Post()
    @UseFilters(new HttpExceptionFilter())
    async create(@Body() createCatDto: CreateCatDto) {
      throw new ForbiddenException();
    }
  • 위에서는 인스턴스화해서 지정했지만, 클래스명만 지정할 경우 인스턴스화는 프레임워크에 맡기고 Dependency Injection 하도록 할 수 있다.
    • DI 를 사용하는 쪽이 메모리 사용량도 적고, 인스턴스를 재사용할 수 있어 권장된다.
    @Post()
    @UseFilters(HttpExceptionFilter)
    async create(@Body() createCatDto: CreateCatDto) {
      throw new ForbiddenException();
    }
controller 레벨 Exception filter 적용
  • 해당 controller 에 정의된 모든 route handler 에 필터가 적용된다.
    @UseFilters(new HttpExceptionFilter())
    export class CatsController {}
global 레벨 Exception filter 적용
  • Global 로 적용할 경우, 모든 controller 와 모든 route handler 에 적용된다.
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();
  • 위처럼 module 외부에서 등록된 필터는 주입할 수 없는 문제가 있기 때문에 module 에 지정할 수 있다.
    • 단, 특정 module에서 지정하긴 하지만, 해당 module에서만 사용하는 것이 아니라 전역으로 적용된다.
    • 그렇기 때문에 보통 filter 를 정의하는 module 에 지정하도록 권장하고 있다.
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}

custom filter 정의

  • BaseExceptionFilter 를 상속하여 커스텀 필터를 정의할 수 있다.
    • catch 메소드를 정의해야한다.
    • 필터는 직접 new 로 인스턴스화하지 않고, 프레임워크에서 자동으로 인스턴스화하도록 한다.
import { Catch, ArgumentsHost } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';

@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    super.catch(exception, host);
  }
}
  • 직접 인스턴스화하지 않기 때문에, global 필터로 적용할 때에는 아래와 같은 방법을 사용한다.
      1. 상기한 APP_FILTER 를 이용하는 방법
      1. HttpAdapter 를 주입하는 방법
async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const { httpAdapter } = app.get(HttpAdapterHost);
  app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));

  await app.listen(3000);
}
bootstrap();

references

0개의 댓글