2026.03.18

HTTP 상태코드는 어떻게 설계해야 할까?

이전 글에서 예외 처리와 ErrorCode를 설계하면서

HTTP 상태코드를 함께 사용했다.

그런데 개발을 하면서 한 가지 고민이 생겼다.

👉 “HTTP 상태코드는 도대체 어떤 기준으로 써야 할까?”

처음에는 단순하게 생각했다.

  • 에러면 그냥 400
  • 서버 문제면 500

하지만 프로젝트를 진행하면서 느낀 건

👉 상태코드를 제대로 쓰지 않으면 API 의미가 흐려진다는 것이었다.


왜 HTTP 상태코드가 중요한가?

HTTP 상태코드는 단순한 숫자가 아니다.

👉 클라이언트와 서버가 약속하는 “의미 있는 신호”다.

예를 들어,

  • 400 → 요청이 잘못됨
  • 404 → 리소스 없음
  • 409 → 충돌 발생

이걸 제대로 구분하지 않으면

  • 프론트에서 분기 처리가 어려워지고
  • API 문서의 신뢰도가 떨어지고
  • 디버깅이 힘들어진다

내가 사용한 상태코드 기준

프로젝트에서는 상태코드를 다음 기준으로 나눴다.


1. 400 Bad Request – “요청 자체가 잘못된 경우”

👉 사용 기준

  • 입력값이 잘못된 경우
  • 필수 값이 없는 경우
  • Validation 실패

예를 들어

INVALID_KEYWORD(400,"POST_003","검색 키워드는 공백일 수 없습니다.")

👉 핵심

“요청을 수정하면 해결되는 문제”


2. 401 / 403 – 인증과 권한 문제

이 둘은 항상 헷갈리는데 기준을 명확히 잡았다.

✔️ 401 Unauthorized

👉 인증 자체가 안 된 경우

  • 로그인 안 함
  • 토큰 없음 / 만료
AUTH_REQUIRED(401,"USER_002","인증이 필요합니다.")

✔️ 403 Forbidden

👉 인증은 되었지만 권한이 없는 경우

  • 다른 사람 리소스 접근
  • 접근 권한 없음
CATEGORY_FORBIDDEN(403,"CATEGORY_003","해당 카테고리에 대한 권한이 없습니다.")

👉 정리하면

  • 401 → “누군지 모름”
  • 403 → “누군지는 알지만 권한 없음”

3. 404 Not Found – 리소스를 찾을 수 없는 경우

👉 사용 기준

  • 존재하지 않는 데이터 조회
  • 이미 삭제된 데이터
USER_NOT_FOUND(404,"USER_001","유저를 찾을 수 없습니다.")

👉 핵심

“요청은 정상인데 대상이 없음”


4. 409 Conflict – 데이터 충돌

이건 초반에 많이 고민했던 부분이다.

처음에는 중복도 400으로 처리했었는데

의미적으로 맞지 않다고 느꼈다.

그래서 이렇게 분리했다.

DUPLICATED_EMAIL(409,"USER_004","이미 사용 중인 이메일입니다.")

👉 409를 쓰는 이유

  • 중복은 “요청이 잘못된 게 아니라”
  • “현재 상태와 충돌하는 것”

👉 그래서 400이 아니라 409가 더 적절하다.


5. 500 Internal Server Error – 서버 문제

👉 사용 기준

  • 예상하지 못한 예외
  • 시스템 오류
INTERNAL_SERVER_ERROR(500,"COMMON_500","서버 오류가 발생했습니다.")

👉 핵심

“클라이언트가 해결할 수 없는 문제”


설계하면서 중요하게 생각한 기준

상태코드를 선택할 때 항상 이 질문을 했다.

👉 “이 문제를 누가 해결할 수 있는가?”

  • 클라이언트가 수정 가능 → 400
  • 인증 문제 → 401 / 403
  • 리소스 없음 → 404
  • 데이터 충돌 → 409
  • 서버 문제 → 500

이 기준 하나로 대부분 정리가 됐다.


적용 후 느낀 점

처음에는 상태코드를 이렇게까지 나눌 필요가 있나 싶었다.

하지만 적용하고 나서 확실히 달라졌다.

  • API 의미가 훨씬 명확해졌고
  • 프론트와의 협업이 쉬워졌고
  • 디버깅이 빨라졌다

특히

👉 상태코드만 보고도 상황을 어느 정도 파악할 수 있게 된 점이 가장 컸다.