Skip to Content
문서가이드에러 처리

에러 처리

ASAPJS는 두 단계의 에러 처리 메커니즘을 제공합니다. Wrapper 함수의 try-catch 블록이 핸들러 에러를 1차로 잡고 errorToResponse()를 통해 응답을 생성하며, Express errorHandler 미들웨어가 Wrapper 바깥에서 발생한 에러를 2차로 처리합니다.

에러 시스템 개요

ASAPJS에는 두 가지 에러 클래스가 있습니다:

클래스패키지필드용도
HttpError@asapjs/errorstatus, errorCode, message, data?타입 안전한 에러 생성 (권장)
HttpException@asapjs/routerstatus, message간단한 에러 또는 레거시 호환

HttpError와 error() 팩토리 (권장)

@asapjs/error 패키지는 타입 안전한 에러 생성 시스템을 제공합니다. error() 팩토리로 에러 생성자를 정의하면, 코드 문자열 기반의 구조화된 에러와 Swagger 스키마 자동 등록을 함께 사용할 수 있습니다.

import { error } from '@asapjs/error'; import { TypeIs } from '@asapjs/schema';

HttpError 클래스

export class HttpError extends Error { readonly status: number; readonly errorCode: string; readonly message: string; readonly data?: Record<string, any>; constructor(status: number, errorCode: string, message: string, data?: Record<string, any>); toJSON(): HttpErrorBody; } export interface HttpErrorBody { status: number; errorCode: string; message: string; data?: Record<string, any>; }

error() 팩토리로 에러 정의

error() 함수는 재사용 가능한 에러 생성자(ErrorCreator)를 반환합니다. 스키마를 지정하면 data 필드에 타입 안전성이 적용되고, Swagger 문서에 에러 스키마가 자동 등록됩니다.

import { error } from '@asapjs/error'; import { TypeIs } from '@asapjs/schema'; // 에러 생성자 정의 const UserNotFound = error( 404, // HTTP 상태 코드 'USER_NOT_FOUND', // 에러 코드 (문자열) '사용자 {userId}를 찾을 수 없습니다', // 메시지 템플릿 ({key}로 보간) { userId: TypeIs.INT() } // data 스키마 (타입 + Swagger) ); // 에러 던지기 — data가 타입 검사됨 throw UserNotFound({ userId: 42 }); // → HttpError { status: 404, errorCode: 'USER_NOT_FOUND', message: '사용자 42를 찾을 수 없습니다', data: { userId: 42 } }
파라미터타입설명
statusnumberHTTP 상태 코드
codestring에러 식별 코드 (예: 'USER_NOT_FOUND'). Swagger 스키마 이름으로도 사용됨
messagestring메시지 템플릿. {key} 형식으로 data 필드 값을 보간
schemaRecord<string, SchemaType>data 필드의 타입 스키마. TypeIs 타입을 사용

ErrorCreator 메타데이터

error()가 반환하는 ErrorCreator 함수에는 Swagger 문서화를 위한 메타데이터가 저장됩니다:

export interface ErrorCreator<T = any> { (data: T): HttpError; _status: number; _code: string; _message: string; _schema: Record<string, any>; }

라우트 데코레이터의 errors 옵션에 전달하면 해당 엔드포인트의 Swagger 에러 응답이 자동 생성됩니다:

import { Get, ExecuteArgs, RouterController } from '@asapjs/router'; const PostNotFound = error(404, 'POST_NOT_FOUND', '게시글 {postId}을 찾을 수 없습니다', { postId: TypeIs.INT(), }); export default class PostController extends RouterController { @Get('/:postId', { title: '게시글 상세 조회', response: PostInfoDto, errors: [PostNotFound], // Swagger에 에러 응답 자동 등록 }) async getPost({ path }: ExecuteArgs) { const postId = parseInt((path as any)?.postId as string, 10); const post = await this.postService.getPost(postId); if (!post) { throw PostNotFound({ postId }); } return post; } }

HttpException (레거시/간단한 경우)

@asapjs/router에서 제공하는 간단한 에러 클래스입니다. errorCodedata 필드 없이 statusmessage만 필요한 경우에 사용할 수 있습니다.

import { HttpException } from '@asapjs/router';

클래스 정의

export class HttpException extends Error { public status: number; public message: string; constructor( status: number = 500, message: string = '알 수 없는 서버 오류가 발생했습니다.' ) { super(message); this.status = status; this.message = message; } }

사용 예시

import { HttpException } from '@asapjs/router'; throw new HttpException(404, 'Post not found'); throw new HttpException(401, '인증이 필요합니다'); throw new HttpException(403, '권한이 없습니다');

참고: HttpException으로 던진 에러는 WrappererrorToResponse()에 의해 errorCode: 'LEGACY_HTTP_EXCEPTION'으로 매핑됩니다. 구조화된 에러 코드가 필요하다면 @asapjs/errorerror() 팩토리 또는 HttpError를 사용하세요.

Wrapper의 에러 처리 흐름

모든 라우트 핸들러는 Wrapper 함수로 감싸집니다. Wrapper의 try-catch 블록이 핸들러에서 발생한 에러를 잡아 @asapjs/errorerrorToResponse()를 통해 HTTP 응답으로 변환합니다.

// Wrapper 내부 에러 처리 (packages/router/src/utils/wrapper.ts) try { const output = await cb(args); if (output) { res.status(200).json(output); } } catch (err) { const isServerError = err == null || typeof err !== 'object' || (err as { status?: number }).status === 500 || (err as { status?: number }).status === undefined; if (isServerError) { logger.error('[SERVER ERROR]', err); if ((getConfig() as any).sentry !== undefined) { Sentry.captureException(err); } } errorToResponse(err, res); }

에러 처리 순서

  1. 에러 발생 시 isServerError 여부를 판별 (err가 null이거나 status가 500 또는 undefined인 경우)
  2. 서버 에러인 경우에만 logger.error로 로그 기록 및 Sentry 캡처 (설정된 경우)
  3. 모든 에러가 errorToResponse(err, res)를 통해 응답으로 변환됨

errorToResponse()의 에러 분류

errorToResponse() 함수는 에러 객체의 종류에 따라 다른 응답을 생성합니다:

에러 유형조건응답 형식
HttpErrorerror instanceof HttpError{ status, errorCode, message, data? }
HttpException (레거시)statusmessage는 있지만 errorCode가 없음{ status, errorCode: 'LEGACY_HTTP_EXCEPTION', message }
HttpErrorBody 형태의 객체status, errorCode, message 프로퍼티가 모두 있음객체를 그대로 전달
일반 에러 / 알 수 없는 에러위 조건에 해당하지 않음{ status: 500, errorCode: 'INTERNAL_SERVER_ERROR', message: '...' }

Express errorHandler 미들웨어

RouterPlugin에서 Express 미들웨어 체인의 마지막에 등록되는 에러 핸들러입니다. Wrapper 바깥에서 발생한 에러(예: 커스텀 미들웨어에서 next(error) 호출)를 처리합니다.

// packages/router/src/middleware/errorHandler.ts const errorHandler = ( error: HttpException, req: Request, res: Response, next: NextFunction ) => { const { status = 500, message } = error; res.status(status).json({ status, message }); };

RouterPlugininit() 메서드에서 this.app.use(errorHandler)로 등록됩니다:

// packages/router/src/plugin.ts async init(config, context) { this.initMiddlewares(); await this.initRouter(config.dirname); this.app.use(errorHandler); // 마지막에 등록 }

참고: 이 미들웨어는 HttpException만 처리하며 { status, message } 2필드 형식으로 응답합니다. Wrapper 안에서 발생한 에러는 이 미들웨어에 도달하지 않고 errorToResponse()를 통해 처리됩니다.

에러 처리 패턴

error() 팩토리 사용 (권장)

import { error } from '@asapjs/error'; import { TypeIs } from '@asapjs/schema'; import { RouterController, Get, ExecuteArgs } from '@asapjs/router'; const PostNotFound = error(404, 'POST_NOT_FOUND', '게시글을 찾을 수 없습니다', {}); const Unauthorized = error(401, 'UNAUTHORIZED', '인증이 필요합니다', {}); export default class PostController extends RouterController { @Get('/:postId', { title: '게시글 상세 조회', response: PostInfoDto, errors: [PostNotFound], }) async getPost({ path }: ExecuteArgs) { const postId = parseInt((path as any)?.postId as string, 10); const post = await this.postService.getPost(postId); if (!post) { throw PostNotFound({}); } return post; } }

HttpException 사용 (간단한 경우)

import { HttpException } from '@asapjs/router'; throw new HttpException(404, '게시글을 찾을 수 없습니다');

권장: 일반 Error를 던지면 Wrapper에서 statusundefined로 처리되어 항상 HTTP 500, errorCode: 'INTERNAL_SERVER_ERROR'로 응답됩니다. 클라이언트에 적절한 상태 코드를 전달하려면 HttpError 또는 HttpException을 사용하세요.

Sentry 연동

config.sentry가 설정되어 있으면, 서버 에러(status 500 또는 undefined) 발생 시 Sentry.captureException(err)으로 자동 보고됩니다. Sentry 캡처는 내부적으로만 수행되며, 에러 응답 자체는 errorToResponse()가 생성하는 표준 형식을 따릅니다.

// config에 sentry 설정이 있는 경우 if ((getConfig() as any).sentry !== undefined) { Sentry.captureException(err); // 캡처만 수행 } errorToResponse(err, res); // 응답은 표준 형식

Sentry 초기화는 Application 클래스가 initModules() 단계에서 자동으로 수행합니다:

// config에 sentry 필드가 있으면 자동 초기화 { sentry: { dsn: 'https://...', environment: 'production', // 기본값: 'development' } }

에러 응답 형식

HttpError (error() 팩토리)

{ "status": 404, "errorCode": "POST_NOT_FOUND", "message": "게시글을 찾을 수 없습니다", "data": {} }

HttpException (레거시)

{ "status": 404, "errorCode": "LEGACY_HTTP_EXCEPTION", "message": "Post not found" }

서버 에러 (500)

{ "status": 500, "errorCode": "INTERNAL_SERVER_ERROR", "message": "알 수 없는 서버 오류가 발생했습니다." }

관련 문서

Last updated on