📖

스토리텔링 형식으로 업데이트되었습니다! 실무 사례와 함께 더 쉽게 이해할 수 있어요.

이미지 로딩 중...

일관된 에러 응답 설계 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 11. 25. · 3 Views

일관된 에러 응답 설계 완벽 가이드

API 개발에서 가장 중요하면서도 간과하기 쉬운 에러 응답 설계를 다룹니다. 클라이언트와 서버가 명확하게 소통할 수 있는 표준화된 에러 응답 체계를 구축하는 방법을 배웁니다.


목차

  1. 에러_응답_표준_포맷
  2. HTTP_상태_코드_선택_가이드
  3. 에러_메시지_작성_원칙
  4. 에러_코드_체계_설계
  5. 상세_에러_정보_vs_보안
  6. 다국어_에러_메시지_처리

1. 에러_응답_표준_포맷

김개발 씨는 프론트엔드 팀에서 걸려온 전화를 받았습니다. "서버에서 에러가 나는데, 어떤 에러인지 도저히 알 수가 없어요." 확인해보니 어떤 API는 {error: "실패"}, 다른 API는 {message: "오류 발생"}, 또 다른 API는 그냥 문자열 "Error"만 반환하고 있었습니다.

에러 응답 표준 포맷은 모든 API가 동일한 구조로 에러를 반환하도록 정의한 규약입니다. 마치 우체국에서 모든 우편물에 동일한 양식의 반송 스티커를 붙이는 것과 같습니다.

표준 포맷을 사용하면 클라이언트가 에러를 예측 가능하게 처리할 수 있고, 디버깅 시간이 획기적으로 줄어듭니다.

다음 코드를 살펴봅시다.

// 표준 에러 응답 인터페이스 정의
interface ErrorResponse {
  success: false;
  error: {
    code: string;           // 에러 고유 코드 (예: "AUTH_001")
    message: string;        // 사용자에게 보여줄 메시지
    details?: unknown;      // 추가 정보 (선택)
    timestamp: string;      // 에러 발생 시각
    path: string;           // 요청 경로
  };
}

// 실제 에러 응답 예시
const errorResponse: ErrorResponse = {
  success: false,
  error: {
    code: "USER_NOT_FOUND",
    message: "요청하신 사용자를 찾을 수 없습니다.",
    timestamp: new Date().toISOString(),
    path: "/api/users/123"
  }
};

김개발 씨는 입사 6개월 차 백엔드 개발자입니다. 어느 날 프론트엔드 팀 이프론트 씨에게서 다급한 메시지를 받았습니다.

"김개발 님, 회원가입 API에서 에러가 나는데 뭐가 문제인지 모르겠어요." 김개발 씨가 확인해보니 에러 응답이 그저 {"error": "fail"}이었습니다. 이게 이메일 형식 오류인지, 비밀번호 규칙 위반인지, 서버 장애인지 전혀 알 수 없었습니다.

옆자리 박시니어 씨가 슬쩍 모니터를 보더니 한숨을 쉬었습니다. "에러 응답에 표준이 없으니까 이런 일이 생기는 거예요.

우리 팀 API들 에러 형식이 다 제각각이에요." 에러 응답 표준 포맷이란 정확히 무엇일까요? 쉽게 비유하자면, 병원에서 사용하는 진단서 양식과 같습니다.

어느 병원을 가든 진단서에는 환자명, 병명, 진단일, 담당의 정보가 동일한 위치에 적혀 있습니다. 덕분에 다른 병원에서도 그 진단서를 바로 이해할 수 있습니다.

API의 에러 응답도 마찬가지입니다. 표준 포맷이 없던 시절에는 어땠을까요?

개발자 A는 {error: "message"}를, 개발자 B는 {err: {msg: "..."}}를, 개발자 C는 그냥 문자열을 반환했습니다. 프론트엔드 개발자는 API마다 다른 에러 처리 로직을 작성해야 했습니다.

새로운 팀원이 합류하면 각 API의 에러 형식을 일일이 파악해야 했습니다. 바로 이런 문제를 해결하기 위해 표준 에러 포맷이 필요합니다.

표준 포맷의 핵심 필드를 살펴보겠습니다. 먼저 success 필드는 요청 성공 여부를 명확히 알려줍니다.

에러 응답에서는 항상 false입니다. code는 에러의 고유 식별자로, 클라이언트가 에러 종류를 구분하는 데 사용합니다.

message는 사용자에게 보여줄 친절한 설명입니다. timestamp는 에러 발생 시각으로, 로그 추적에 필수입니다.

path는 어떤 엔드포인트에서 에러가 발생했는지 알려줍니다. 실제 현업에서는 어떻게 활용할까요?

대형 이커머스 서비스를 생각해보세요. 결제 실패, 재고 부족, 배송지 오류 등 수십 가지 에러가 발생할 수 있습니다.

표준 포맷이 있으면 앱 개발자는 하나의 에러 처리 로직으로 모든 API를 다룰 수 있습니다. QA팀도 에러 로그를 일관되게 분석할 수 있습니다.

주의할 점도 있습니다. 표준을 정했으면 반드시 모든 API에 일관되게 적용해야 합니다.

99개의 API가 표준을 따르더라도 1개가 다르면 그 1개 때문에 클라이언트 코드가 복잡해집니다. 또한 표준 포맷은 팀 전체가 합의하고 문서화해야 합니다.

다시 김개발 씨 이야기로 돌아가봅시다. 박시니어 씨의 조언을 듣고 김개발 씨는 팀 회의에서 에러 응답 표준화를 제안했습니다.

한 달 후, 프론트엔드 팀과의 협업이 훨씬 수월해졌습니다.

실전 팁

💡 - 표준 포맷을 TypeScript 인터페이스로 정의하고 팀 전체가 공유하세요

  • 성공 응답도 {success: true, data: ...} 형태로 통일하면 일관성이 높아집니다

2. HTTP_상태_코드_선택_가이드

김개발 씨는 코드 리뷰에서 당황스러운 지적을 받았습니다. "에러가 나는데 왜 200 OK를 반환해요?" 김개발 씨는 응답 본문에 에러 메시지를 넣었으니 괜찮다고 생각했는데, 선배는 고개를 저었습니다.

HTTP 상태 코드는 서버가 클라이언트에게 요청 처리 결과를 알려주는 세 자리 숫자입니다. 마치 교통 신호등처럼 빨간불(4xx, 5xx)은 문제가 있다는 신호이고, 초록불(2xx)은 정상이라는 신호입니다.

올바른 상태 코드를 사용해야 클라이언트가 에러를 자동으로 감지하고 적절히 대응할 수 있습니다.

다음 코드를 살펴봅시다.

// HTTP 상태 코드별 에러 응답 예시
class ApiError extends Error {
  constructor(
    public statusCode: number,
    public code: string,
    message: string
  ) {
    super(message);
  }
}

// 400: 잘못된 요청 (클라이언트 실수)
throw new ApiError(400, "INVALID_EMAIL", "이메일 형식이 올바르지 않습니다.");

// 401: 인증 필요 (로그인 안 됨)
throw new ApiError(401, "AUTH_REQUIRED", "로그인이 필요합니다.");

// 403: 권한 없음 (로그인은 했지만 접근 불가)
throw new ApiError(403, "FORBIDDEN", "이 리소스에 접근 권한이 없습니다.");

// 404: 리소스 없음
throw new ApiError(404, "USER_NOT_FOUND", "사용자를 찾을 수 없습니다.");

// 500: 서버 내부 오류
throw new ApiError(500, "INTERNAL_ERROR", "서버 오류가 발생했습니다.");

김개발 씨는 회원가입 API를 만들면서 고민에 빠졌습니다. 이메일 형식이 잘못되었을 때 어떤 상태 코드를 반환해야 할까요?

그냥 200에 에러 메시지를 담으면 안 될까요? 박시니어 씨가 설명을 시작했습니다.

"HTTP 상태 코드는 단순한 숫자가 아니에요. 전 세계 개발자들이 합의한 약속이죠." HTTP 상태 코드를 교통 신호등에 비유해봅시다.

초록불(2xx)은 "통과해도 좋습니다"라는 의미입니다. 요청이 성공적으로 처리되었다는 뜻이죠.

노란불(3xx)은 "다른 곳으로 가세요", 즉 리다이렉션입니다. 빨간불(4xx)은 "당신 잘못이에요", 클라이언트가 뭔가 실수했다는 신호입니다.

마지막으로 보라색 불(5xx)은 "서버가 고장났어요"라는 의미입니다. 왜 올바른 상태 코드가 중요할까요?

첫째, 클라이언트 라이브러리가 상태 코드를 보고 자동으로 에러를 감지합니다. axios 같은 HTTP 클라이언트는 4xx, 5xx 응답을 자동으로 에러로 처리합니다.

200을 반환하면 클라이언트는 성공으로 인식합니다. 둘째, 모니터링 시스템이 상태 코드를 기반으로 서비스 상태를 파악합니다.

500 에러가 급증하면 알람이 울립니다. 하지만 모든 에러를 200으로 반환하면 모니터링이 무용지물이 됩니다.

가장 자주 사용하는 상태 코드를 정리해봅시다. 400 Bad Request는 클라이언트가 잘못된 데이터를 보냈을 때 사용합니다.

이메일 형식 오류, 필수 필드 누락 등이 해당합니다. 401 Unauthorized는 인증이 필요한데 로그인하지 않았을 때입니다.

403 Forbidden은 로그인은 했지만 권한이 없을 때 사용합니다. 404 Not Found는 요청한 리소스가 존재하지 않을 때입니다.

409 Conflict는 이미 존재하는 이메일로 가입하려 할 때처럼 충돌 상황에 사용합니다. 422 Unprocessable Entity는 형식은 맞지만 비즈니스 규칙에 어긋날 때 사용합니다.

500 Internal Server Error는 서버에서 예상치 못한 오류가 발생했을 때입니다. 이건 개발자가 고쳐야 할 버그입니다.

503 Service Unavailable은 서버가 일시적으로 과부하이거나 점검 중일 때 사용합니다. 실무에서 자주 하는 실수가 있습니다.

"사용자를 찾을 수 없음"을 500으로 반환하는 경우가 있습니다. 하지만 이건 서버 오류가 아니라 클라이언트가 잘못된 ID를 요청한 것이므로 404가 맞습니다.

반대로 데이터베이스 연결 실패를 400으로 반환하는 것도 잘못입니다. 이건 서버 문제이므로 500이 맞습니다.

김개발 씨는 고개를 끄덕였습니다. "상태 코드가 그냥 숫자가 아니라 중요한 의미를 담고 있군요."

실전 팁

💡 - 401과 403을 헷갈리지 마세요: 401은 "누구세요?", 403은 "알겠지만 안 돼요"

  • 확신이 없으면 400(클라이언트 잘못)과 500(서버 잘못)부터 구분하세요

3. 에러_메시지_작성_원칙

김개발 씨는 사용자 게시판에서 불만 글을 발견했습니다. "결제하려는데 '오류가 발생했습니다'만 뜨고 뭘 어떻게 해야 하는지 모르겠어요." 에러 메시지 하나가 고객 이탈로 이어지고 있었습니다.

좋은 에러 메시지는 사용자에게 무엇이 잘못되었는지, 왜 그런지, 어떻게 해결할 수 있는지를 알려줍니다. 마치 친절한 안내원이 길을 잃은 손님에게 현재 위치, 목적지, 가는 방법을 모두 설명해주는 것과 같습니다.

모호한 메시지는 사용자를 당황하게 만들고, 고객센터 문의를 증가시킵니다.

다음 코드를 살펴봅시다.

// 나쁜 에러 메시지 예시
const badMessages = {
  error1: "오류가 발생했습니다.",           // 무슨 오류인지 모름
  error2: "실패",                           // 정보가 전혀 없음
  error3: "NullPointerException at line 42" // 기술 용어 노출
};

// 좋은 에러 메시지 구조
interface UserFriendlyError {
  what: string;    // 무엇이 잘못되었는지
  why: string;     // 왜 그런지 (선택)
  how: string;     // 어떻게 해결하는지
}

// 좋은 에러 메시지 예시
const goodMessages = {
  emailInvalid: {
    what: "이메일 주소 형식이 올바르지 않습니다.",
    how: "example@domain.com 형식으로 입력해주세요."
  },
  paymentFailed: {
    what: "결제를 완료할 수 없습니다.",
    why: "카드 한도가 초과되었습니다.",
    how: "다른 카드를 사용하거나 카드사에 문의해주세요."
  }
};

김개발 씨는 결제 시스템을 담당하고 있습니다. 어느 날 CS팀에서 연락이 왔습니다.

"결제 오류 문의가 하루에 50건이 넘어요. 화면에 뭐라고 나오는지 확인해주세요." 확인해보니 에러 메시지가 그저 "결제 처리 중 오류가 발생했습니다"였습니다.

카드 한도 초과인지, 카드 정보 오류인지, 서버 문제인지 전혀 알 수 없었습니다. 박시니어 씨가 조언했습니다.

"에러 메시지는 기술 문서가 아니라 사용자와의 대화예요." 좋은 에러 메시지의 세 가지 원칙을 알아봅시다. 첫 번째 원칙은 **무엇(What)**입니다.

사용자에게 정확히 무엇이 잘못되었는지 알려주세요. "오류가 발생했습니다"가 아니라 "입력하신 이메일 주소를 찾을 수 없습니다"처럼 구체적으로 말해야 합니다.

두 번째 원칙은 **왜(Why)**입니다. 가능하다면 원인도 설명해주세요.

"비밀번호가 일치하지 않습니다"보다 "비밀번호는 대문자를 포함해야 합니다"가 더 도움이 됩니다. 물론 보안상 이유로 자세히 알려주면 안 되는 경우도 있습니다.

세 번째 원칙은 **어떻게(How)**입니다. 사용자가 다음에 무엇을 해야 하는지 안내해주세요.

"잠시 후 다시 시도해주세요", "고객센터(1234-5678)로 문의해주세요"처럼 구체적인 행동 지침을 제공합니다. 피해야 할 에러 메시지 유형이 있습니다.

기술 용어 노출은 금물입니다. "SQLException: duplicate key"같은 메시지는 개발자에게는 유용하지만 일반 사용자는 당황합니다.

비난하는 어조도 피해야 합니다. "잘못된 입력입니다"보다 "다시 확인해주세요"가 부드럽습니다.

모호한 메시지는 최악입니다. "실패", "에러", "오류"만으로는 아무것도 알 수 없습니다.

개발자용 메시지와 사용자용 메시지를 분리하세요. 로그에는 상세한 기술 정보를 기록하고, 사용자에게는 친절한 메시지만 보여주세요.

예를 들어 내부적으로는 "PostgreSQL connection timeout after 30s"를 기록하고, 사용자에게는 "일시적인 서버 문제가 발생했습니다. 잠시 후 다시 시도해주세요"라고 안내합니다.

실무에서 자주 하는 실수가 있습니다. 에러 메시지를 하드코딩하면 나중에 수정하기 어렵습니다.

에러 메시지는 별도의 상수나 설정 파일로 관리하세요. 또한 너무 자세한 정보는 보안 위험이 됩니다.

"해당 이메일은 이미 가입되어 있습니다"는 공격자에게 유용한 정보가 될 수 있습니다. 김개발 씨는 에러 메시지를 전면 개편했습니다.

한 달 후 CS팀에서 기쁜 소식이 왔습니다. "결제 관련 문의가 절반으로 줄었어요!"

실전 팁

💡 - 에러 메시지는 사용자 테스트를 거쳐 개선하세요

  • 에러 메시지도 UX의 일부입니다. 디자이너, 기획자와 함께 검토하세요

4. 에러_코드_체계_설계

김개발 씨는 6개월 전에 작성한 코드를 다시 보다가 에러 메시지 "AUTH_ERROR"를 발견했습니다. 이게 로그인 실패인지, 토큰 만료인지, 권한 부족인지 도저히 기억이 나지 않았습니다.

체계적인 에러 코드의 필요성을 절감한 순간이었습니다.

에러 코드 체계는 에러를 고유하게 식별하고 분류하는 명명 규칙입니다. 마치 도서관의 분류 번호처럼 에러 코드만 보고도 어떤 종류의 에러인지, 어느 모듈에서 발생했는지 파악할 수 있습니다.

잘 설계된 에러 코드는 디버깅 시간을 크게 단축시키고, 클라이언트에서 에러별 분기 처리를 가능하게 합니다.

다음 코드를 살펴봅시다.

// 에러 코드 체계 설계 예시
// 형식: {도메인}_{카테고리}_{상세} 또는 {도메인}_{숫자코드}

const ErrorCodes = {
  // 인증 관련 (AUTH)
  AUTH_TOKEN_EXPIRED: "AUTH_001",      // 토큰 만료
  AUTH_TOKEN_INVALID: "AUTH_002",      // 유효하지 않은 토큰
  AUTH_LOGIN_FAILED: "AUTH_003",       // 로그인 실패
  AUTH_PERMISSION_DENIED: "AUTH_004",  // 권한 없음

  // 사용자 관련 (USER)
  USER_NOT_FOUND: "USER_001",          // 사용자 없음
  USER_EMAIL_DUPLICATE: "USER_002",    // 이메일 중복
  USER_INVALID_PASSWORD: "USER_003",   // 비밀번호 규칙 위반

  // 주문 관련 (ORDER)
  ORDER_NOT_FOUND: "ORDER_001",        // 주문 없음
  ORDER_ALREADY_CANCELLED: "ORDER_002",// 이미 취소된 주문
  ORDER_PAYMENT_FAILED: "ORDER_003",   // 결제 실패
} as const;

// 클라이언트에서의 활용
if (error.code === "AUTH_001") {
  // 토큰 만료 시 자동 로그아웃 처리
  logout();
}

김개발 씨네 팀은 급하게 성장하는 스타트업에서 일하고 있습니다. 서비스가 커지면서 API가 100개를 넘어갔고, 에러 종류도 수백 가지가 되었습니다.

문제는 에러 코드가 뒤죽박죽이라는 것이었습니다. "ERROR_1", "FAIL", "AUTH_ERROR", "E001" 등 일관성 없는 코드들이 혼재했습니다.

새로 합류한 개발자가 "E001이 뭐예요?"라고 물으면 아무도 바로 대답하지 못했습니다. 박시니어 씨가 팀 회의에서 제안했습니다.

"에러 코드 체계를 새로 잡읍시다. 도서관 분류 번호처럼요." 에러 코드 체계의 핵심 원칙을 알아봅시다.

첫째, 도메인 접두사를 사용합니다. AUTH, USER, ORDER, PAYMENT처럼 에러가 발생한 영역을 나타내는 접두사를 붙입니다.

코드만 봐도 어느 모듈과 관련된 에러인지 바로 알 수 있습니다. 둘째, 고유한 식별자를 부여합니다.

같은 도메인 내에서도 세부 에러를 구분할 수 있어야 합니다. "AUTH_TOKEN_EXPIRED"와 "AUTH_TOKEN_INVALID"는 비슷해 보이지만 완전히 다른 상황입니다.

전자는 재발급이 필요하고, 후자는 로그인부터 다시 해야 합니다. 셋째, 확장 가능해야 합니다.

새로운 에러가 추가되어도 기존 체계와 충돌하지 않아야 합니다. 숫자 코드를 사용한다면 여유 있게 번호를 할당하세요.

실제 체계를 설계해봅시다. 많이 사용하는 형식은 {도메인}_{숫자코드} 또는 {도메인}_{카테고리}_{상세}입니다.

예를 들어 "AUTH_001" 또는 "AUTH_TOKEN_EXPIRED" 방식입니다. 전자는 간결하고, 후자는 직관적입니다.

팀 성향에 맞게 선택하세요. 에러 코드는 반드시 문서화해야 합니다.

코드와 의미, HTTP 상태 코드, 발생 상황, 해결 방법을 표로 정리해두세요. 이 문서가 있으면 새 팀원 온보딩이 훨씬 수월해집니다.

클라이언트에서는 어떻게 활용할까요? 에러 코드를 기반으로 분기 처리가 가능합니다.

"AUTH_001(토큰 만료)"이면 토큰 재발급을 시도하고, "AUTH_004(권한 없음)"이면 권한 요청 페이지로 이동합니다. 에러 코드 없이 메시지만으로는 이런 자동화가 불가능합니다.

주의할 점도 있습니다. 에러 코드를 너무 세분화하면 관리가 어려워집니다.

반대로 너무 뭉뚱그리면 의미가 없어집니다. 적절한 수준을 찾는 것이 중요합니다.

또한 한번 배포된 에러 코드는 함부로 변경하면 안 됩니다. 클라이언트가 해당 코드에 의존하고 있을 수 있습니다.

김개발 씨 팀은 2주간의 논의 끝에 에러 코드 체계를 정립했습니다. 이제 에러 로그만 봐도 어떤 문제인지 바로 파악할 수 있게 되었습니다.

실전 팁

💡 - 에러 코드는 변경하지 마세요. 대신 deprecated 처리하고 새 코드를 추가하세요

  • 에러 코드 목록을 API 문서에 포함시키면 프론트엔드 개발자가 고마워합니다

5. 상세_에러_정보_vs_보안

보안팀에서 긴급 메일이 왔습니다. "로그인 API에서 '해당 이메일은 존재하지 않습니다'라고 응답하는데, 이거 보안 취약점입니다." 김개발 씨는 사용자 편의를 위해 친절하게 알려준 것인데, 그게 문제라니 당황스러웠습니다.

상세 에러 정보와 보안은 서로 상충하는 가치입니다. 개발자와 사용자에게는 상세한 정보가 도움이 되지만, 공격자에게도 유용한 정보가 될 수 있습니다.

마치 집 열쇠를 현관 매트 아래에 두면 편하지만 도둑에게도 편한 것과 같습니다. 환경과 상황에 따라 정보 노출 수준을 조절해야 합니다.

다음 코드를 살펴봅시다.

// 환경별 에러 응답 분기 처리
interface ErrorResponse {
  code: string;
  message: string;
  details?: unknown;      // 개발 환경에서만 포함
  stack?: string;         // 개발 환경에서만 포함
}

function formatError(error: Error, env: string): ErrorResponse {
  const baseResponse: ErrorResponse = {
    code: "INTERNAL_ERROR",
    message: "서버 오류가 발생했습니다."
  };

  // 개발 환경에서는 상세 정보 제공
  if (env === "development") {
    return {
      ...baseResponse,
      details: error.message,
      stack: error.stack
    };
  }

  // 운영 환경에서는 최소 정보만 제공
  return baseResponse;
}

// 로그인 에러 메시지 - 보안 고려
// Bad: "해당 이메일이 존재하지 않습니다" (이메일 존재 여부 노출)
// Good: "이메일 또는 비밀번호가 올바르지 않습니다" (정보 숨김)

김개발 씨는 친절한 에러 메시지를 자랑스러워했습니다. "이메일이 존재하지 않습니다", "비밀번호가 틀렸습니다" 등 사용자가 무엇을 고쳐야 하는지 정확히 알려주었으니까요.

그런데 보안팀 최보안 씨가 설명했습니다. "이 메시지 덕분에 공격자가 어떤 이메일이 가입되어 있는지 알아낼 수 있어요.

이메일 열거 공격이라고 합니다." 공격자는 이메일 목록을 대입해보며 "존재하지 않습니다"가 아닌 "비밀번호가 틀렸습니다"가 나오는 이메일만 추려낼 수 있습니다. 그 이메일들로 비밀번호 대입 공격을 하면 됩니다.

상세 정보가 위험한 사례를 더 살펴봅시다. 데이터베이스 오류 메시지가 그대로 노출되면 테이블 구조가 드러납니다.

"Column 'user_password' cannot be null"이라는 메시지는 공격자에게 귀중한 정보입니다. 스택 트레이스가 노출되면 어떤 프레임워크와 라이브러리를 사용하는지, 파일 경로는 어떻게 되는지 알 수 있습니다.

그렇다면 어떻게 균형을 잡아야 할까요? 첫째, 환경별로 분리합니다.

개발 환경에서는 디버깅을 위해 상세 정보를 제공하고, 운영 환경에서는 최소 정보만 노출합니다. 운영에서는 "서버 오류가 발생했습니다"라고만 표시하고, 상세 내용은 서버 로그에 기록합니다.

둘째, 민감한 영역을 구분합니다. 인증, 결제, 개인정보 관련 에러는 특히 조심해야 합니다.

"이메일 또는 비밀번호가 올바르지 않습니다"처럼 무엇이 틀렸는지 구체적으로 알려주지 않습니다. 반면 상품 검색이나 게시글 조회 같은 영역은 상대적으로 자유롭게 정보를 제공해도 됩니다.

셋째, 로깅을 철저히 합니다. 사용자에게 보여주지 않는 정보도 서버에는 기록해야 합니다.

문제 발생 시 로그를 통해 원인을 파악할 수 있어야 합니다. 에러 ID를 발급해서 사용자에게 보여주면, 고객센터 문의 시 해당 ID로 로그를 찾을 수 있습니다.

실무에서 자주 실수하는 부분이 있습니다. 개발 환경 설정이 운영에 그대로 배포되는 경우가 있습니다.

환경 변수를 확실히 확인하세요. 또한 에러 핸들러에서 예외가 발생하면 원본 에러가 그대로 노출될 수 있습니다.

에러 핸들러 자체도 try-catch로 감싸세요. 김개발 씨는 로그인 에러 메시지를 수정했습니다.

사용자 편의는 조금 떨어졌지만, 보안팀에서 엄지를 치켜세웠습니다. "보안과 편의 사이에서 균형을 잘 잡았네요."

실전 팁

💡 - 에러 ID를 발급하여 "문제가 지속되면 에러 코드 ABC123을 알려주세요"라고 안내하세요

  • 보안 감사 시 에러 응답도 점검 대상입니다. 미리 준비해두세요

6. 다국어_에러_메시지_처리

기획팀에서 기쁜 소식이 왔습니다. "우리 서비스가 일본과 베트남에도 진출합니다!" 김개발 씨는 축하하면서도 걱정이 됐습니다.

에러 메시지가 전부 한국어인데, 외국 사용자들은 어떻게 하지?

다국어 에러 메시지는 사용자의 언어 설정에 따라 적절한 언어로 에러를 안내하는 기능입니다. 마치 국제 공항에서 안내 방송이 여러 언어로 나오는 것과 같습니다.

핵심은 에러 코드와 메시지를 분리하고, 메시지는 별도의 언어 리소스 파일에서 관리하는 것입니다.

다음 코드를 살펴봅시다.

// 다국어 에러 메시지 관리 시스템
// 언어별 메시지 리소스 파일
const errorMessages: Record<string, Record<string, string>> = {
  ko: {
    "USER_NOT_FOUND": "사용자를 찾을 수 없습니다.",
    "AUTH_REQUIRED": "로그인이 필요합니다.",
    "INVALID_EMAIL": "이메일 형식이 올바르지 않습니다."
  },
  en: {
    "USER_NOT_FOUND": "User not found.",
    "AUTH_REQUIRED": "Authentication required.",
    "INVALID_EMAIL": "Invalid email format."
  },
  ja: {
    "USER_NOT_FOUND": "ユーザーが見つかりません。",
    "AUTH_REQUIRED": "ログインが必要です。",
    "INVALID_EMAIL": "メールアドレスの形式が正しくありません。"
  }
};

// 에러 응답 생성 함수
function createErrorResponse(code: string, locale: string = "ko") {
  const messages = errorMessages[locale] || errorMessages["ko"];
  return {
    success: false,
    error: {
      code,
      message: messages[code] || "오류가 발생했습니다."
    }
  };
}

김개발 씨는 글로벌 서비스 준비로 바빠졌습니다. 가장 먼저 해야 할 일은 에러 메시지 다국어 지원이었습니다.

하지만 코드 곳곳에 한국어 메시지가 하드코딩되어 있었습니다. 박시니어 씨가 조언했습니다.

"메시지를 코드에서 분리해야 해요. 국제화(i18n) 패턴을 적용합시다." 국제화의 핵심 원칙을 알아봅시다.

첫째, 코드와 메시지를 분리합니다. 코드에는 에러 코드(키)만 남기고, 실제 메시지는 별도의 리소스 파일로 관리합니다.

언어별로 ko.json, en.json, ja.json 파일을 만들어 같은 키에 다른 메시지를 매핑합니다. 둘째, 사용자 언어를 감지합니다.

HTTP 요청의 Accept-Language 헤더를 확인하거나, 사용자가 설정한 언어 정보를 참조합니다. "ko-KR,ko;q=0.9,en-US;q=0.8"처럼 우선순위가 있는 경우 가장 높은 우선순위를 사용합니다.

셋째, 폴백(fallback) 처리를 합니다. 요청한 언어의 메시지가 없으면 기본 언어(보통 영어나 한국어)로 응답합니다.

메시지 자체가 없으면 에러 코드라도 표시합니다. 메시지 작성 시 주의할 점이 있습니다.

문화적 차이를 고려하세요. 같은 의미라도 나라마다 표현 방식이 다릅니다.

직역하지 말고, 해당 언어 네이티브 스피커의 검수를 받는 것이 좋습니다. 변수 처리도 중요합니다.

"{{count}}개의 파일을 업로드했습니다"처럼 변수를 사용할 때, 영어는 복수형 처리가 필요합니다(1 file vs 2 files). 실제 구현 방식을 살펴봅시다.

가장 간단한 방법은 JSON 파일에 키-값 쌍으로 저장하는 것입니다. 프로젝트가 커지면 i18next(JavaScript), react-intl(React), vue-i18n(Vue) 같은 라이브러리를 사용합니다.

이런 라이브러리는 변수 치환, 복수형 처리, 날짜/숫자 포맷팅까지 지원합니다. 클라이언트와 서버의 역할 분담도 중요합니다.

방법 1은 서버에서 번역하는 것입니다. 서버가 사용자 언어를 파악해서 번역된 메시지를 반환합니다.

클라이언트는 받은 메시지를 그대로 표시하면 됩니다. 방법 2는 클라이언트에서 번역하는 것입니다.

서버는 에러 코드만 반환하고, 클라이언트가 자체 리소스 파일에서 메시지를 찾습니다. 각각 장단점이 있습니다.

서버 번역은 일관성이 높지만 서버 부담이 커집니다. 클라이언트 번역은 유연하지만 리소스 파일 동기화가 필요합니다.

많은 서비스가 두 방식을 혼용합니다. 김개발 씨 팀은 2주간의 작업 끝에 다국어 에러 메시지 시스템을 완성했습니다.

일본 출시 첫날, 현지 사용자들이 한국어가 아닌 일본어로 에러 메시지를 받아보고 만족스러워했습니다.

실전 팁

💡 - 번역 품질이 중요합니다. 구글 번역에만 의존하지 말고 네이티브 검수를 받으세요

  • 새 에러 코드를 추가할 때 모든 언어 파일을 업데이트하는 것을 잊지 마세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#TypeScript#ErrorHandling#APIDesign#HTTPStatus#ErrorResponse#API,에러처리,설계

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.