포시코딩

[Express] 커스텀 에러와 에러 핸들링 로직 분리 본문

Node.js

[Express] 커스텀 에러와 에러 핸들링 로직 분리

포시 2023. 2. 6. 15:02
728x90

개요

사용자 인증 미들웨어를 만들면서 인증 실패하는 각각의 상황에 따라 

return res.status(코드).json(내용);

위 코드를 작성하는게 너무 반복되는걸 보고 다른 더 좋은 방법이 있는지 생각하게 되었다.

 

계속 생각하면서 지금까지 배웠던 것중에

응용이 가능할 것 같은게 두가지 있었는데 다음과 같다.

 

방법 1 - 에러 핸들링

며칠전에 강의를 들으면서 에러 처리를 특이하게 하는걸 발견했는데

app.js에서 맨 마지막에 

app.use(errorHandler);
app.use(notFoundHandler);

위 코드를 위치시켜 놓고

 

import HttpException from "../common/httpException";
import { Request, Response, NextFunction } from "express";

export const errorHandler = (
  error: HttpException,
  request: Request,
  response: Response,
  next: NextFunction
) => {
  const status = error.statusCode || error.status || 500;

  response.status(status).send(error);
};
import { Request, Response, NextFunction } from "express";

export const notFoundHandler = (
  request: Request,
  response: Response,
  next: NextFunction
) => {

  const message = "Resource not found";

  response.status(404).send(message);
};

두 에러 핸들러를 통해 서버에서 나오는 에러들을 제어해서 한번에 처리하고 있는 것이었다.

 

강의가 에러 핸들링에 대한게 아니고 지나가듯 나온 코드라

직접 다 작성해서 테스트 했을 때 404 notFoundHandler만 작동하는걸 확인하고 나도 까먹었었다.

 

방법 2 - 커스텀 에러

지금까지 나는 에러를 아래와 같은 방법으로 사용해왔다.

const err = new Error('메시지');
err.name = '1번에러';
throw err;

이렇게 보내던 에러는 받는곳에서 어떤걸 기준으로 status code를 지정해줘야 할지 애매해서

직접 지정한 name이나 message를 통해 if문 분기를 태우거나 했어야 됐는데

 

Error를 상속받는 Custom Error 클래스를 만들어 사용하면

status code를 직접 부여해서 

new CustomError(401, '권한없음');

이런식으로 보내면 되지 않을까 라는 생각을 하며 막연하게 나중에 해봐야지 하고 보류했다.

 

방법 종합

위 사항들을 종합해보면

특정 상황에 대해 커스텀 에러를 발생시키고 그 에러를 받아내는 

에러 핸들러를 마련해서

특정 위치에서 발생하는 모든 에러들에 대해 분석하여

응답(response) 시키는 로직을 구현하면 되는 것으로 파악됐다.

 

검색을 좀 해보니 마침 내가 딱 찾던 내용의 블로그를 발견할 수 있었다.

 

https://velog.io/@tmpks5/express-에러-핸들링-로직-분리-및-Custom-Error-제작

 

[express] 에러 핸들링 로직 분리 및 Custom Error 제작

모든 서비스 로직을 asyncWrapper가 감싸게되며 모든 에러가 asyncWrapper에서 감지되어ErrorHandlerMiddleware 함수 하나에서 전부 핸들링을 하게 만들었습니다.또한 기존의 express는 정상 응답과 에러 응답

velog.io

 

생각한것과 약간 다른게 있었는데 위 블로그에서는 

asyncWrapper라는 미들웨러를 만들어

내보낼 error가 발생할 수 있는 미들웨어를 감싸 감지해서

핸들러로 보내는 방식으로 진행하고 있었다.

 

근데 이거 없이 진행해도 정상적으로 내 에러 핸들러에서 받을 수 있는걸 확인해서

해당 asyncWrapper 부분은 생략했다.

 

에러가 발생했지만 기본적인 에러 코드나 에러 메시지가 없는 경우에 대해서도

처리를 해주는 코드가 있는데, 

일단 그 부분은 직접 겪으며 추가할 생각으로 마찬가지로 생략하고 진행했다.

 

추가로 Error를 상속하는 CustomError를 만들고

그 CustomError를 상속하는 BadRequestError를 만들어 사용하는 이유를 모르겠더라.

 

여러 커스텀 에러를 만들거면 그냥

Error를 상속하는 BadRequestError, UnAuthorizedError 등을 만들어 사용하면 되지 않나 싶지만

 

일단 이것도 패스.. 내 방식대로 먼저 사용해봐야 겠다.

 

 

에러 핸들러에 관련해서는

 

https://jeonghwan-kim.github.io/node/2017/08/17/express-error-handling.html

 

에러 처리를 위한 익스프레스 가이드

원문: http://thecodebarbarian.com/80-20-guide-to-express-error-handling.html 익스프레스의 에러 처리 미들웨어는 HTTP…

jeonghwan-kim.github.io

위 블로그를 참고했다.

 

블로그 내용중 집중해서 봐야할 내용은 위 글에 전부 담겼다고 봐도 무방하다.

 

이렇게 위 내용들을 종합해서 코드를 구현해보았다.

 

코드 구현

 

CustomError.js

class CustomError extends Error {
  constructor(statusCode, message) {
    super(message);
    this.statusCode = statusCode;
  }
}

module.exports = CustomError;

 

errorHandler.js

const errorHandler = (err, req, res, next) => {
  console.log('errorHandler 도착');
  res.status(err.statusCode).json({ message: err.message });
};

module.exports = errorHandler;

 

app.js

const express = require('express');
const app = express();

// ...생략

const router = require('./routes');
app.use('/', router);

const errorHandler = require('./middlewares/errorHandler');
app.use(errorHandler);

module.exports = app;

 

세팅은 이렇게 끝났고 커스텀 에러를 발생시킬 미들웨어를 하나 예시로 소개해보겠음

 

authMiddleware.js

const CustomError = require('../errors/CustomError');

const authMiddleware = (req, res, next) => {
  console.log('authMiddleware 지나는중');

  const { accessToken, refreshToken } = req.cookies;
  if (!accessToken || !refreshToken) {
    throw new CustomError(401, '로그인 필요');
  }
  // ...생략
  next();
};

module.exports = authMiddleware;

 

accessToken과 refreshToken 없이 요청하게 되면

new CustomError를 발생시켜

내가 원하는 응답을 받는 것을 확인할 수 있었다.

 

정리하며

당장은 middleware에서 에러처리를 쉽게 하기 위해 공부해서 적용해봤는데

 

나중에는 모든 controller, service, repository에서 

try/catch문을 걷어내며 핸들러를 통해 한번에 처리할 수 있게도 해봐야 하고

블로그에서처럼 enum을 통해 status code를 한번에 관리하는 것도 해봐야 겠다.

 

근데 곧 jest도 공부해서 사용하게되면

오늘 공부한 express에서와는 또 다른 방법으로 에러 핸들링을 하게 될텐데

 

그 방법들은 또 어떤 방법으로 처리할 수 있을지 기대된다.

 

추가 내용

 

adminAuthMiddleware.js

const CustomError = require('../errors/CustomError');

const adminAuthMiddleware = async (req, res, next) => {
  console.log('adminAuthMiddleware 지나는중');
  const { accessToken, refreshToken } = req.cookies;
  if (!accessToken || !refreshToken) {
    throw new CustomError(401, '로그인 필요');
  }
  const isAccessTokenValidate = await validateAccessToken(accessToken);
  const isRefreshTokenValidate = await validateRefreshToken(refreshToken);
  // ...생략
  next();
};

module.exports = adminAuthMiddleware;

미들웨어 안에서 await으로 데이터를 가져와야 했는데 

async를 쓰니

에러가 발생하며 진행되지 않는 문제를 겪었다.

 

데이터를 가져오려면 await을 써야 해서 async를 쓸 수 밖에 없는데.. 하며 고민했는데

문득 참고한 블로그에서 asyncWrapper를 쓰던걸 기억하고 적용해봤다.

 

asyncWrapper.js

const asyncWrapper = (fn) => {
	return async (req, res, next) => {
		try {
			await fn(req, res, next)
		} catch (error) {
			next(error)
		}
	}
}

module.exports = { asyncWrapper }

 

adminAuthMiddleware.js

const { asyncWrapper } = require('./asyncWrapper');
const CustomError = require('../errors/CustomError');

const adminsAuthMiddleware = asyncWrapper(async (req, res, next) => {
  console.log('adminsAuthMiddleware 지나는중');
  const { accessToken, refreshToken } = req.cookies;
  if (!accessToken || !refreshToken) {
    throw new CustomError(401, '로그인 필요');
  }
  const isAccessTokenValidate = await validateAccessToken(accessToken);
  const isRefreshTokenValidate = await validateRefreshToken(refreshToken);
  // ...생략
  next();
});

module.exports = adminsAuthMiddleware;

 

asyncWrapper를 통해 감싼 후 사용하니 async를 적용해도 문제없이 작동되는걸 확인할 수 있었다.

역시 다 이유가 있었구나..

 

이렇게 되면 Error를 상속한 CustomError를 상속해서 사용하는 여러 Error 클래스들에 대해서도

이유가 있을 것으로 파악되는데

일단, 그것도 직접 문제를 겪게되면 기억해놨다가 적용해봐야 겠다.

728x90