본문으로 건너뛰기
개발 머꼬
개발 노트API 설계
hohyeon.dev21

API 오류 응답 포맷을 팀마다 다르게 만들다가 표준으로 정리한 과정

  • #API 설계
  • #Engineering Note

문제 발생

서비스마다 오류 응답이 달랐습니다.

{ "error": "NOT_FOUND" }
{ "message": "게시글을 찾을 수 없습니다", "code": 404 }
{ "success": false, "errors": [{ "field": "title", "msg": "필수" }] }

클라이언트는 세 가지 파서를 갖게 됐고, 새 오류가 생길 때마다 문자열 비교가 늘었습니다.

원인 분석

이미 표준이 있는데 매번 새로 만들고 있었습니다. RFC 9457은 목적을 첫 문장에 적습니다 — HTTP API를 위해 새 오류 응답 포맷을 정의할 필요가 없도록, 응답 본문에 기계가 읽을 수 있는 오류 상세를 담는 "problem detail"을 정의한다.

미디어 타입은 application/problem+json이고, 표준 멤버는 다섯입니다.

  • type — 문제 유형을 식별하는 URI 참조. 스펙은 **소비자가 type URI를 문제 유형의 기본 식별자로 사용해야 한다(MUST)**고 못박습니다.
  • title — 문제 유형에 대한 짧고 사람이 읽을 수 있는 요약
  • status — 원 서버가 생성한 HTTP 상태 코드
  • detail — 이번 발생 건에 특정한, 사람이 읽는 설명
  • instance — 이 발생 건을 식별하는 URI 참조

그리고 클라이언트 구현에 대한 지침도 있습니다 — 소비자는 정보를 얻기 위해 detail 멤버를 파싱해서는 안 되며(SHOULD NOT), 확장 멤버가 더 적절하고 오류가 적은 방법입니다.

해결 방안

  1. 응답 형태를 하나로 맞춥니다.
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json

{
  "type": "https://meokko.com/problems/not-post-author",
  "title": "작성자만 수정할 수 있습니다",
  "status": 403,
  "detail": "이 게시글의 작성자가 아닙니다.",
  "instance": "/posts/react-server-component-머꼬-a1b2c3d4"
}
  1. 기계가 분기할 값은 확장 멤버로 넣습니다. 필드 단위 검증 오류가 대표적입니다.
{ "type": "...", "status": 422, "errors": [{ "field": "title", "rule": "required" }] }
  1. type URI는 안정적으로 유지합니다. 클라이언트의 분기 기준이 되므로, 문구를 다듬는다고 URI를 바꾸면 계약이 깨집니다. 문서 페이지를 실제로 열어둘 필요는 없지만 열어두면 좋습니다.

  2. 내부 구현을 흘리지 않습니다. RFC의 보안 고려사항 그대로 — 스택 덤프 같은 구현 세부를 HTTP 인터페이스로 노출하지 않아야 합니다. 서버 구조와 데이터가 드러납니다.

  3. 상태 코드를 대체하지 않습니다. status는 상태 코드의 사본일 뿐입니다. HTTP 상태를 200으로 두고 본문에만 오류를 담는 방식은 캐시·프록시·클라이언트 기본 동작을 전부 어긋나게 만듭니다.

  4. 한 번에 전면 도입하지 않아도 됩니다. 새 엔드포인트부터 이 포맷으로 내고, 기존 것은 클라이언트가 옮겨갈 때 맞춰 바꿉니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.