본문 바로가기
카테고리 없음

Express.js에서의 에러 핸들링 패턴 분석

by BillyCho 2025. 2. 20.

Express.js에서의 에러 핸들링: 세 가지 접근 방식

Express.js 애플리케이션에서 에러를 효과적으로 처리하는 것은 매우 중요합니다. 여기서는 세 가지 주요 에러 핸들링 접근 방식을 살펴보고, 각각의 장단점을 분석해 보겠습니다.

1. Promise 기반 asyncHandler

 
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};
 

장점:

  • 동기 및 비동기 에러를 모두 캐치할 수 있습니다.
  • 모든 종류의 함수(동기/비동기)를 안전하게 처리할 수 있습니다.
  • Promise로 래핑되어 일관된 에러 처리가 가능합니다.

동작 원리:

  1. 함수를 Promise로 감싸 실행합니다.
  2. 에러 발생 시 catch로 잡아 next()를 통해 에러 핸들러로 전달합니다.

2. async/await 기반 asyncHandler

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

장점:

  • 코드가 직관적이고 읽기 쉽습니다.
  • 디버깅이 용이합니다.
  • 동기 및 비동기 에러를 모두 처리할 수 있습니다.

제한사항:

  • async 함수로 래핑되므로, 비동기 컨텍스트에서 최적화되어 있습니다.

3. try-catch 기반 asyncHandler

 
const asyncHandler = (fn) => (req, res, next) => {
  try {
    const result = fn(req, res, next);
    if (result instanceof Promise) {
      result.catch(next);
    }
  } catch (error) {
    next(error);
  }
};
 

장점:

  • 구조가 단순하고 가벼움
  • 동기 코드에 적합함

제한사항:

  • 비동기 에러 처리를 위해서는 추가적인 로직이 필요합니다.

실제 사용 사례 분석

MongoDB 쿼리 처리

 
app.get(
  "/api/contents",
  asyncHandler(async (req, res) => {
    const contents = await Content.find();
    res.json(contents);
  })
);
 
  • Promise 기반: 모든 종류의 에러를 안전하게 처리합니다.
  • async/await 기반: 동기 및 비동기 에러를 모두 처리합니다.
  • try-catch 기반: 즉시 발생하는 에러는 처리하지만, Promise rejection은 별도 처리가 필요합니다.

동기 유효성 검사

const validateTitle = (title) => {
  if (!title) throw new Error('제목 필수');
  return title;
}

app.post("/api/contents", asyncHandler(async (req, res) => {
  const title = validateTitle(req.body.title);
  // ... 나머지 로직
}));
 

모든 접근 방식에서 동기 에러를 잘 처리할 수 있습니다.

디버깅과 로깅

로깅 전략

// 요청 로깅
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);

// 응답 로깅
console.log('Response:', result);

// 에러 로깅
console.error('Error:', error);

Mongoose 디버그 모드

 
mongoose.set('debug', true);

결론

각 접근 방식은 고유한 장단점을 가지고 있습니다:

  • Promise 기반 패턴: 가장 범용적이고 안전하지만, 복잡할 수 있습니다.
  • async/await 패턴: 가독성이 좋고 직관적이며, 대부분의 상황에서 효과적입니다.
  • try-catch 패턴: 단순하고 가벼우나, 비동기 에러 처리에 추가 작업이 필요합니다.

상황과 프로젝트의 요구사항에 따라 적절한 패턴을 선택하는 것이 중요합니다. 대부분의 경우 async/await 패턴이 좋은 균형을 제공하지만, 특수한 요구사항이 있다면 다른 패턴을 고려해볼 수 있습니다.