본문 바로가기
개발

[ Nest.js ] Custom Exception 관리하기

by Crmal 2023. 10. 21.

Nest.js에서 Exception을 각 모듈별로 관리하기 하기위하여 ExceptionFilter를 이용해보기로 하였습니다.

이 글의 최종 형태는 다음과 같습니다.

create(signUpRequestDto: SignUpRequestDto): Observable<Omit<User, 'password'>> {
    this.checkUserAndThrowError(signUpRequestDto.email);

    return this.authService.hashPassword(signUpRequestDto.password).pipe(
      map(hashedPassword => {
        const user = this.userFactory.createUser(signUpRequestDto.email, hashedPassword);
        const { password, ...result } = user;
        this.userRepository.save(user);
        return result as Omit<User, 'password'>;
      }),
    );
  }

  checkUserAndThrowError(email: string): void {
    const user = this.userRepository.findOne({ where: { email } });
    if (user) {
      throw new AuthException(AuthExceptionType.CONFLICT_DUPLICATE_USER);
    }
  }

객체지향적 설계를 위하여 유저 체크하는 부분을 따로 빼주고 싶었으며 exception을 사용할때 각 모듈별로 Exception을 관리하며 오류 종류에 대한 정보도 따로 관리하고자 하였습니다.

1. BaseExceptionType 생성

path: /src/common/exception/exception.type.ts

import { HttpStatus } from '@nestjs/common';

export interface ExceptionType {
  status: HttpStatus;

  timestamp: Date;

  exceptionCode: number;

  message: string;
}

Exception response로 내보낼 interface를 생성합니다.

2. BaseException Class 생성

path: /src/common/exception/base.exception.ts

import { HttpException } from '@nestjs/common';

import { ExceptionType } from './exception.type';

export class BaseException extends HttpException {
  constructor(exceptionType: ExceptionType) {
    super(exceptionType.message, exceptionType.status);
    this.statusCode = exceptionType.exceptionCode;
    this.timestamp = exceptionType.timestamp;
  }

  statusCode: number;

  timestamp: Date;

  path: string;
}

BaseException은 모든 Exception response의 규격을 맞추기위하여 생성하였습니다.
BaseException은 HttpException을 상속받습니다.

3. AuthException Class 생성

path: src/auth/exception/auth.exception.ts

import { BaseException, ExceptionType } from 'src/common/exception';

export class AuthException extends BaseException {
  constructor(exceptionType: ExceptionType) {
    super(exceptionType);
  }
}

만들어둔 BaseException을 그대로 받아 생성합니다.

4. AuthExceptionType Class 생성

path: src/auth/exception/auth.exception.type.ts

import { HttpStatus } from '@nestjs/common';
import { ExceptionType } from 'src/common/exception';

export class AuthExceptionType implements ExceptionType {
  constructor(status: HttpStatus, timestamp: Date, exceptionCode: number, message: string) {
    this.status = status;
    this.timestamp = timestamp;
    this.exceptionCode = exceptionCode;
    this.message = message;
  }

  status: HttpStatus;

  timestamp: Date;

  exceptionCode: number;

  message: string;

  static CONFLICT_DUPLICATE_USER: AuthExceptionType = new AuthExceptionType(
    HttpStatus.CONFLICT,
    new Date(),
    4090,
    '이미 존재하는 유저입니다.',
  );
}

다음과 같이 작성하게 된다면 static CONFLICT_DUPLICATE_USER와 같이 각 모듈별로 Exception을 관리 할 수 있습니다.

5. HttpExceptionFilter 작업

path: src/common/exception/http.excepiton.filter.ts

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

import { BaseException } from './base.exception';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: BaseException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();

    response.status(status).json({
      path: request.url,
      statusCode: exception.statusCode,
      message: exception.message,
      timeStamp: exception.timestamp,
    });
  }
}

다음과 같이 ExceptionFilter를 이용하여 만들어둔 BaseException의 값을 응답해줍니다.

최종 결과는 다음과 같습니다.

 

댓글