심심한 개발자의 취미생활

ExceptionFilter

  • NestJS 예외 필터는 애플리케이션 전반에서 처리되지 않은 모든 예외를 처리하는 역할을 하는 내장된 예외 처리 계층이다.
  • 애플리케이션 코드에서 예외를 잡아서 처리하지 못하면, 이 예외 필터 계층이 해당 예외를 포착하여 자동으로 사용자에게 적절한 응답을 보낸다.

기본 예외 처리

  • 기본적으로 NestJSHttpException 타입 (및 그 하위 클래스)의 예외를 처리하는 Global Exception Filter를 내장 하고 있다.
  • 만약 처리되지 않은 예외가 HttpException이 아니거나 HttpException을 상속 받지 않은 알 수 없는 예외일 경우 다음과 같은 기본 JSON 응답을 생성한다.
{
    "statusCode": 500,
    "message": "Internal server error"
}

표준 예외 처리 (Throwing Standard Exceptions)

  • 일반적인 HTTP 기반 애플리케이션에서는 특정 오류 조건이 발생했을 때 표준 HTTP 응답 객체를 보내는 것이 가장 좋다.
  • NestJS@nestjs/common 패키지에서 HttpException 클래스를 제공하여 이를 쉽게 처리할 수 있도록 지원한다.
@Get()
async getError() {
    throw new HttpException('Forbiddne', HttpStatus.FORBIDDEN);
}
{
    "statusCode": 403,
    "message": "Forbidden"
}
  • HttpException 생성자는 응답 본문과 HTTP 상태 코드를 인자로 받으며, 응답 본문 전체를 커스텀 객체로 덮어 쓸 수도 있다.

커스텀 예외 (Custom Exceptions)

  • 명확한 에러 처리를 위해 자신만의 예외 계층을 만드는 것이 좋다. Custom ExceptionNestJS의 기본 HttpException을 상속 받아 만들 수 있다.
  • NestJSBadRequestException, UnauthorizedException, NotFoundException 등 일반적인 HTTP 예외에 대한 클래스들을 내장하고 있어 편리하게 사용할 수 있다.
// forbidden.exception.ts
export class ForbiddentException extends HttpException {
    constructor() {
        super('Forbidden', HttpStatus.FORBIDDEN);
    }
}

throw new ForbiddenException();

예외 필터 제작

  • 내장된 예외 필터만으로 부족할 경우, 예외 처리 계층을 제어할 수 있다.
  • 로깅을 추가하거나 동적인 요소에 따라 다른 JSON 스키마를 사용하고자 할때 예외 필터를 직접 구현하여 사용할 수 있다.
  • 예외 필터는 ExceptionFilter 인터페이스를 구현해야 하며, catch(exception: T, host ArgumentsHost) 메서드를 가진다.
  • @Catch(HttpException) 데코레이터를 사용하여 특정 타입의 예외만 처리하도록 지정할 수 있다.
// http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException) // HttpException 타입의 예외만 처리하도록 지정
export class HttpExceptionFilter implements ExceptionFilter {
    catch(exception: HttpException, host ArgumentsHost) { // ArgumentsHost는 현재 처리 중인 Request, Response 객체에 접근을 지원하는 유틸리티 객체이다. 이를 통해 응답을 직접 제어할 수 있다.
        const ctx = host.switchToHttp();
        const request = ctx.getRequest<Request>();
        const response = ctx.getResponse<Response>();
        const status = exception.getStatus();

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

필터 바인딩 (Binding Filters)

  • 제작한 예외 필터는 다양한 레벨(Method, Controller, Global 등) 에서 적용할 수 있다.

메서드 범위

  • 특정 라우트 핸들러에만 필터를 적용
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
    throw new ForbiddenException();
}

컨트롤러 범위

  • 컨트롤러 내의 모든 라우트 핸들러에 필터를 적용
@Controller('cats')
@UseFilters(new HttpExceptionFilter())
export class CatsController {}

전역 범위

  • 애플리케이션 전체에 필터를 적용하는 것으로 main.ts 파일에서 설정
// main.ts
async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    app.useGlobalFilter(new HttpExceptionFilter())
    await app.listen(3000)
}
  • 전역 필터에 의존성 주입이 필요한 경우, 모듈의 providersAPP_FILTER 토큰을 사용하여 등록 할 수 있다.

모든 예외 처리 (Catch EveryThing)

  • 처리되지 않은 모든 종류의 예외를 잡으려면 @Catch() 데코레이터의 인자를 비워두면 된다.
  • 이를 통해 어떤 타입의 예외든 모두 처리하는 필터를 만들 수 있다.
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
    // 예외 처리
}

상속 (Inheritance)

  • 기본 전역 예외 필터의 동작을 확장하고자 할떄 BaseExceptionFilter를 상속 받고 catch()메서드에서 super.catch()를 호출하여 기본 필터에 예외 처리를 위힘할 수 있다.
@Catch()
export class AllExceptionFilter extends BaseExceptionFilter {
    catch(exception: unknown, host: ArgumentsHost) {
        super.catch(exception, host)
    }
}