Exception Filter - 예외 메시지 반환

HanSH·2024년 1월 17일

NestJS

목록 보기
7/29

NestJS는 애플리케이션 전체에 모든 예외를 처리하는 예외 계층이 내장되어있다 한다.
기본적으로 HttpException유형을 처리하고, 예외가 인식되지 않은 경우 아래의 기본 응답을 반환한다.

{
  "statusCode": 500,
  "message": "Internal server error"
}

기본 정의 예외

Controller에서 아래와 같이 기본 HttpException을 발생시킬 수 있다!

throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
// throw new HttpException(message: string | object, status_code: number, options: Optional);

이 경우 아래의 응답을 볼 수 있다.

{
  "statusCode": 403, // HTTP 상태 코드로 정의되어있다. @nestjs/common의 HttpStatus 코드를 사용하는 것이 좋다.
  "message": "Forbidden"
}

이를 이용해서 try-catch문을 작성해보면

try {
  await this.catsService.findAll()
} catch (error) { 
  throw new HttpException({
    status: HttpStatus.FORBIDDEN,
    error: 'This is a custom message',
  }, HttpStatus.BAD_REQUEST, {
    cause: error
  });
}
// throw new HttpException(html 본문에 보여지는 내용, response 코드(브라우저 콘솔), 마지막은 몰????루);

응답이 다음과 같이 온다.

사용자 정의 예외

사용자 정의 예외를 사용할 일이 거의 없겠지만, 만약 사용한다면 다음을 따르는 것이 좋다. Nest가 예외를 인식하고 오류 응답을 자동으로 처리하기 떄문.
사용자 정의 예외가 기본 HttpException 클래스를 상속하는 계층 구조를 만드는 것이 좋다.

예를 들면 이런식으로.

export class ForbiddenException extends HttpException {
  constructor() {
    super('Forbidden', HttpStatus.FORBIDDEN);
  }
}

기본 HTTP 예외들

@nestjs/common 패키지에 존재하는데, 우린 모두 ctrl+. 쓸거잖아?

  • BadRequestException
  • UnauthorizedException
  • NotFoundException
  • ForbiddenException
  • NotAcceptableException
  • RequestTimeoutException
  • ConflictException
  • GoneException
  • HttpVersionNotSupportedException
  • PayloadTooLargeException
  • UnsupportedMediaTypeException
  • UnprocessableEntityException
  • InternalServerErrorException
  • NotImplementedException
  • ImATeapotException
  • MethodNotAllowedException
  • BadGatewayException
  • ServiceUnavailableException
  • GatewayTimeoutException
  • PreconditionFailedException

이 경우에는 Exception(message, error: {cause, description})으로 사용하면 된다. statusCode는 알아서 작성해줌!

예외 필터

예외가 떴을 때 로그를 작성하고 싶다거나 할때는 예외 필터를 사용하자.
이 경우에는 response를 응답 내용을 내 맘대로 정할 수 있다!

HttpException을 포착하면 사용자 정의 응답 로직을 구현하는 예외 필터를 만들어보자. 아마 아래와 같이 작성할 수 있을 것이다.

// http-exception.filter.ts
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,
      });
  }
}

* 참고로, 모든 예외 필터는 ExceptionFilter<T> 인터페이스를 상속받고 catch 함수를 구현해야한다.
@Catch(HttpException) 데코레이터는 이 필터가 HttpException 유형의 예외만 찾고있음을 알린다. 물론 여러 개의 유형을 넣을 수 도 있다.

예외 필터의 적용

이제 만든 HttpExceptionFilterCatsController에 적용해보자.

  • 방법이 2가지가 있는데,

1) 인스턴스를 만들어 filter에 적용하는 방법

  @Post()
  @UseFilters(new HttpExceptionFilter())
  async create(@Body() createCatDto: CreateCatDto) {
    throw new ForbiddenException();
    this.catsService.create(createCatDto);
  }

2) 인스턴스 생성 책임을 프레임워크에 맡기는 방법

  @Post()
  @UseFilters(HttpExceptionFilter)
  async create(@Body() createCatDto: CreateCatDto) {
    throw new ForbiddenException();
    this.catsService.create(createCatDto);
  }

nest 공식 문서에서는 인스턴스 대신 클래스를 사용하는 것을 선호한다고 한다.

  • 또한 클래스 전체에 적용할 수도 있다.
@UseFilters(new HttpExceptionFilter())
export class CatsController {}
  • Global하게 적용할 수도 있다!
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();

주의!!!!!! useGlobalFilter 메서드는 게이트 웨이, 하이브리드 애플리케이션에 대한 필터를 설정하진 않는다.

  • 모듈에도 적용이 가능하다!
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';

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

다만, 모듈 외부에서 등록한 전역 필터는 모든 모듈의 문맥 외부에서 수행되므로 종속성을 주입할 수 없다. 따라서 위의 방법을 사용하여 모든 모듈에 전역 범위 필터를 등록할 수 있다.

모든 예외를 잡아보자.

@Catch 데코레이터에 아무것도 넣지 않으면 모든 예외를 잡을 수 있다.

예외 처리를 기본 필터에 위임하는 방법은?

@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter { // 일단 상속받고
  catch(exception: unknown, host: ArgumentsHost) { // catch를 구현한 다음
    super.catch(exception, host); // super의 catch를 호출하자.
  }
}

이때 BaseExceptionFilternew로 인스턴스화 해서는 안된다!

profile
저는 말하는 싹 난 감자입니다

0개의 댓글