NestJS에서 예외 처리 커스터마이징: 전역 예외 필터와 커스텀 예외 클래스

su_under·2024년 12월 2일
2
post-thumbnail

개발을 하다 보면 예외 처리의 중요성을 실감하게 된다. 잘못된 입력 데이터, 데이터베이스 연결 오류, 혹은 서버의 예기치 못한 동작은 사용자 경험에 큰 영향을 미친다. NestJS는 이런 문제를 체계적으로 해결할 수 있는 예외 처리 기능을 제공하며, 이를 커스터마이징하면 프로젝트의 요구사항에 맞는 효과적인 처리가 가능한다. 이번 글에서는 NestJS의 예외 처리 메커니즘을 활용해 안정적이고 일관된 예외 처리 방식을 구현하는 방법을 다뤄보겠다.

1. 글로벌 예외 필터 구현하기

NestJS는 기본적으로 제공하는 예외 처리 메커니즘 외에 @Catch() 데코레이터를 활용하여 글로벌 예외 필터를 구현할 수 있다.

@Catch() 데코레이터의 역할

@Catch() 데코레이터는 NestJS에서 예외 필터를 정의하고 특정 유형의 예외를 처리할 수 있도록 지정하는 데 사용된다. 이 데코레이터를 통해 어떤 예외를 포착하고 처리할 것인지 선언할 수 있다.

  • 특정 예외에만 적용하는 경우
    • @Catch(exceptionType) 형태로 사용하여 특정 예외 타입에 대해 동작하는 필터를 구현할 수 있다.
    • 예를 들어, 아래 코드는 HttpException 유형의 예외만 포착한다.
      @Catch(HttpException)
      export class HttpExceptionFilter implements ExceptionFilter {
          catch(exception: HttpException, host: ArgumentsHost) {
              // HttpException 처리 로직
          }
      }
  • 모든 예외에 적용하는 경우
    • 데코레이터에 매개변수를 생략하면 모든 예외를 포착하도록 설정된다.
      @Catch()
      export class GlobalExceptionsFilter implements ExceptionFilter {
          catch(exception: unknown, host: ArgumentsHost) {
              // 모든 예외 처리 로직
          }
      }
  • 다수의 예외 처리도 가능
    • @Catch()는 여러 예외를 처리하도록 설정할 수도 있다.
      @Catch(NotFoundException, UnauthorizedException)
      export class SpecificExceptionsFilter implements ExceptionFilter {
          catch(exception: HttpException, host: ArgumentsHost) {
              // NotFoundException 및 UnauthorizedException 처리
          }
      }

@Catch() 데코레이터는 특정 예외와 정의된 필터를 연결한다. 이렇게 정의된 필터는 예외가 발생할 때 자동으로 실행된다. 이제 실제로 글로벌 예외 필터를 적용해보자.

글로벌 예외 필터 적용하기

import {
    ExceptionFilter,
    Catch,
    ArgumentsHost,
    HttpException,
    HttpStatus,
    Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';

@Catch()
export class GlobalExceptionsFilter implements ExceptionFilter {
    catch(exception: unknown, host: ArgumentsHost): void {

        const ctx = host.switchToHttp();
        const response = ctx.getResponse<Response>();
        const request = ctx.getRequest<Request>();

        // 기본 상태코드가 없는 경우, Internal Server Error로 처리
        const status =
            exception instanceof HttpException
                ? exception.getStatus()
                : HttpStatus.INTERNAL_SERVER_ERROR;

        // 기본 메시지를 커스텀 메시지로 설정
        const message =
            status === HttpStatus.INTERNAL_SERVER_ERROR
                ? '서버에서 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'
                : (exception as HttpException).message ||
                  '알 수 없는 오류가 발생했습니다.';

        // 응답 형식 설정
        response.status(status).json({
            statusCode: status,
            timestamp: new Date().toISOString(),
            path: request.url,
            message,
        });

        // 서버 로그 출력
        const logger: Logger = new Logger('GlobalExceptionsFilter');
        logger.error(exception);
    }
}

@Catch() 데코레이터를 통해 GlobalExceptionsFilter가 모든 예외(unknown)를 처리할 수 있도록 구현했다. 500 INTERNAL SERVER ERROR인 경우, '서버에서 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'라는 메시지를 반환한다. 그 외 상태 코드에서는 HttpException의 메시지 또는 '알 수 없는 오류가 발생했습니다.'라는 메시지를 반환할 것이다.

main.ts에 app.useGlobalFilters(new GlobalExceptionsFilter()) 를 추가하면 필터가 전역으로 적용된다. 필터를 적용한 결과는 다음과 같다.


2. 커스텀 예외 클래스 작성하기

NestJS는 예외 처리를 커스터마이징할 수 있도록 HttpException 클래스를 제공한다. 이를 상속받아 프로젝트에 특화된 예외를 정의하면 특정 상황에서 발생하는 예외를 명확히 구분할 수 있을 뿐만 아니라 반복되는 예외 처리 로직을 줄이고 재사용성을 높일 수 있다.

커스텀 예외 클래스 적용하기

NestJS의 HttpException을 상속받아 예외를 정의한다. HttpException은 두 가지 주요 매개변수를 받는다:

  • response: 클라이언트에게 반환할 메시지
  • status: HTTP 상태 코드
import { HttpException, HttpStatus } from '@nestjs/common';

export class NotFoundSessionException extends HttpException {
    constructor() {
        super('세션 게시물을 찾을 수 없습니다.', HttpStatus.NOT_FOUND);
    }
}

export class NotFoundEventException extends HttpException {
    constructor() {
        super('이벤트를 찾을 수 없습니다.', HttpStatus.NOT_FOUND);
    }
}

export class NotFoundUserException extends HttpException {
    constructor(userId: number) {
        super(
            `ID가 ${userId}인 사용자를 찾을 수 없습니다.`,
            HttpStatus.NOT_FOUND,
        );
    }
}

super() 메서드를 호출해 기본 상태 코드(HttpStatus.NOT_FOUND)와 메시지를 설정하였다. 예외에 더 많은 정보를 포함하고 싶다면, NotFoundUserException 클래스와 같이 생성자에서 매개변수를 받아 처리할 수 있다.

아래와 같이 리포지토리에 커스텀 예외를 적용해 보았다.

import { Prisma } from '@prisma/client';
import { NotFoundSessionException } from '../../../global/exception/custom.exception'

@Injectable()
export class SessionRepository {

    async getSession(sessionId: number): Promise<SessionEntity> {
        const session: SessionEntity = await this.prisma.session.findUnique({
            where: {
                id: sessionId,
                isDeleted: false,
            },
            include: {
                user: true,
            },
        });

        if (!session) {
            throw new NotFoundSessionException();
        }
        return session;
    }
}

필터를 적용한 결과는 다음과 같다.


이번 글에서는 NestJS의 예외 처리 커스터마이징에 대해 살펴보았다. 글로벌 예외 필터로 전역적인 처리 방식을 설정하고, 커스텀 예외 클래스를 통해 모듈별로 특화된 예외를 정의하면 시스템 안정성과 사용자 경험을 동시에 높일 수 있다. 또한 하나의 파일에 여러개의 예외를 한 번에 관리할 수 있어 매우 편리하다. 예외 처리를 커스터마이징 해보면서 그동안 아무렇게나 구현했던 예외 처리도 효율적으로 할 수 있다는 것을 알게 됐다!.!

profile
솨의 개발일기

2개의 댓글

comment-user-thumbnail
2024년 12월 3일

유익한 내용 잘 읽었습니다

1개의 답글