Project/foliohub

[Express] Custom Error Handling

솔B 2024. 4. 6. 23:57

프론트엔드 개발자가 소소한 취미로 백앤드를 개발해 본 경험 글로, 틀린 점이 있다면 댓글로 부탁드립니다 🙏

 

에러 처리를 어떻게 하면 좋을까 찾아보다가 에러 핸들링 미들웨어를 만들어 사용하는 예시들을 발견했다!

이를 활용해 에러 처리를 한 곳에서 관리하고 응답의 일관성을 유지할 수 있다는 점이 깔끔해 보여 적용해 봤다.

 

에러 처리 미들웨어

기본적으로 에러 처리 미들웨어는 반드시 4개의 인수(err, req, res, next)를 가져야 한다!

app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

 

 

 

커스텀 에러 처리 핸들링

기존 Error 클래스를 상속받아 커스텀 에러 클래스를 만들어준다.

import { ErrorResponse, ErrorType, ErrorValidation } from '../types/error';

export class CustomError extends Error {
  private httpStatusCode: number; // HTTP 상태 코드
  private errorType: ErrorType; // 에러의 종류
  private errors: string[] | null; // 에러 메시지를 저장하는 배열
  private errorRaw: any; // 에러의 원시 정보
  private errorsValidation: ErrorValidation[] | null; // 유효성 검사 에러를 저장하는 배열

  constructor(
    httpStatusCode: number,
    errorType: ErrorType,
    message: string,
    errors: string[] | null = null,
    errorRaw: any = null,
    errorsValidation: ErrorValidation[] | null = null
  ) {
    super(message);

    this.name = this.constructor.name;

    this.httpStatusCode = httpStatusCode;
    this.errorType = errorType;
    this.errors = errors;
    this.errorRaw = errorRaw;
    this.errorsValidation = errorsValidation;
  }

  get HttpStatusCode() {
    return this.httpStatusCode;
  }

  get JSON(): ErrorResponse { // JSON 형식의 응답을 정의
    return {
      errorType: this.errorType,
      errorMessage: this.message,
      errors: this.errors,
      errorRaw: this.errorRaw,
      errorsValidation: this.errorsValidation,
      stack: this.stack,
    };
  }
}

 

관련 타입 정의는 아래와 같다.

export type ErrorResponse = {
  errorType: ErrorType;
  errorMessage: string;
  errors: string[] | null;
  errorRaw: any;
  errorsValidation: ErrorValidation[] | null;
  stack?: string;
};

export type ErrorType = 'General' | 'Raw' | 'Validation' | 'Unauthorized' | 'Forbidden';

export type ErrorValidation = { [key: string]: string };

 

 

에러를 처리하는 미들웨어를 만들어 보겠다.

middlewares 폴더 아래에 파일을 생성해 준다.

error로는 위에서 만들어 준 CustomError를  첫 번째 매개변수로 받는다.
여기서는 error, req, res, next가 모두 있어야 한다!!

 

응답값으로는 http 상태코드와 JSON 형태의 에러를 리턴해준다.

 

middlewares/errorHandler.ts

import { Request, Response, NextFunction } from 'express';
import { CustomError } from '../libs/customError';

const errorHandler = (error: CustomError, req: Request, res: Response, next: NextFunction) => {
  return res.status(error.HttpStatusCode).json(error.JSON);
};

export default errorHandler;

 

이렇게 만들어 준 error 미들웨어를 적용해 주면! 끝이다!

src/index.ts

app.use(errorHandler);

 

사용 시에는 아래와 같이 사용해 주면 간단하고 깔끔하다!

import { Request, Response, NextFunction } from 'express';
import { CustomError } from '../../libs/customError';
import { AppDataSource } from '../../data-source';
import { User } from '../../entities/User';
import { Portfolio } from '../../entities/Portfolio';
import { prependCloudinaryBaseUrl } from '../../libs/utils';

/**
 * 포트폴리오 metadata
 * GET /v1/portfolio/metadata
 */
export const metadataPortfolio = async (req: Request, res: Response, next: NextFunction) => {
  const { username } = req.query;

  if (typeof username !== 'string')
    return next(new CustomError(400, 'Validation', 'username이 string 타입이 아닙니다.'));

  try {
    const UserRepository = AppDataSource.getRepository(User);
    const user = await UserRepository.findOne({
      where: {
        username: username,
      },
      select: ['id'],
    });
    if (!user) {
      return next(new CustomError(404, 'General', '해당 user가 존재하지 않습니다.'));
    }
   ...
  } catch (error) {
    return next(new CustomError(400, 'Raw', 'Error', null, error));
  }
};

 

참고자료

 

GitHub - mkosir/typeorm-express-typescript: 💊 Minimal Express RESTful API boilerplate. Spin it up with single command. TypeSc

💊 Minimal Express RESTful API boilerplate. Spin it up with single command. TypeScript, TypeORM, Postgres and Docker with focus on best practices and painless developer experience. - mkosir/typeorm-...

github.com