Article

HTTP 상태 코드 완벽 이해 및 API 개발 가이드

도입: API 성공 판단의 핵심

웹 서비스는 클라이언트와 서버가 HTTP 프로토콜로 통신합니다. 그 과정에서 요청이 성공했는지, 실패했는지를 판단하는 기준이 바로 HTTP 상태 코드입니다.

현장에서는 모든 응답을 200으로 처리하거나 모든 오류를 500으로 반환하는 관례 없는 API를 자주 봅니다. 이는 클라이언트 개발자가 응답 본문을 파싱해야만 결과를 알 수 있게 만들고, 모니터링과 캐싱 전략을 수립하기 어렵게 합니다. 결국 팀 간 의사소통 오류와 버그 증가로 이어집니다.

HTTP 상태 코드 3분류: 최소 합의사항

API를 개발할 때, 팀이 함께 따라야 할 최소 기준은 응답을 3가지로 분류하는 것입니다:

  • 2xx: 요청 성공 (Success)
  • 4xx: 클라이언트 오류 (Client Error) — 사용자 입력 검증 실패, 인증/인가 문제
  • 5xx: 서버 오류 (Server Error) — 예상치 못한 서버 문제, 외부 서비스 장애

2xx 성공 응답: 요청이 정상 처리됨

2xx 상태 코드는 서버가 요청을 정상적으로 처리했음을 의미합니다. 클라이언트는 추가 오류 처리 없이 응답 본문의 데이터를 사용할 수 있습니다.

코드이름용도
200OKGET, PUT, PATCH 요청 성공 — 응답 본문에 결과 데이터 포함
201CreatedPOST로 새 리소스 생성 성공 — Location 헤더에 생성된 리소스 URI 포함
204No ContentDELETE, PATCH 성공 — 반환할 내용이 없음 (응답 본문 비어있음)
304Not Modified조건부 요청(If-None-Match) 성공 — 클라이언트 캐시 사용 가능

실무 예시

GET 요청 성공 (200 OK)

GET /api/users/1
HTTP/1.1 200 OK
Content-Type: application/json

{"id": 1, "name": "John", "email": "john@example.com"}

리소스 생성 (201 Created)

POST /api/users
HTTP/1.1 201 Created
Location: /api/users/2
Content-Type: application/json

{"id": 2, "name": "Jane", "email": "jane@example.com"}

삭제 성공 (204 No Content)

DELETE /api/users/1
HTTP/1.1 204 No Content

4xx 클라이언트 오류: 사용자 입력/권한 문제

4xx 상태 코드는 클라이언트의 잘못된 요청으로 인한 오류를 나타냅니다. 서버는 정상이지만 사용자가 잘못 요청했거나, 인증/인가가 부족하거나, 요청 형식이 맞지 않는 경우입니다.

코드이름상황
400Bad Request요청 형식 오류, 필수 파라미터 누락, 유효하지 않은 데이터 타입
401Unauthorized인증 정보 없거나 만료됨 — 로그인 필요
403Forbidden인증은 완료했지만 권한 부족 — 관리자 권한 필요 등
404Not Found요청한 리소스가 존재하지 않음
405Method Not Allowed엔드포인트가 해당 HTTP 메서드를 지원하지 않음
409Conflict기존 리소스와 충돌 — 중복 생성 시도 등
422Unprocessable Entity요청 형식은 유효하지만 비즈니스 규칙 위반 — 유효성 검사 실패
429Too Many RequestsAPI 호출 제한 초과 — Rate Limiting

실무 예시

필수 필드 누락 (400 Bad Request)

POST /api/users
Content-Type: application/json

{"name": "John"}  # email 필드 누락

HTTP/1.1 400 Bad Request
{"error": "email is required"}

인증 정보 없음 (401 Unauthorized)

GET /api/admin/dashboard

HTTP/1.1 401 Unauthorized
{"error": "Authentication required"}

권한 부족 (403 Forbidden)

DELETE /api/admin/users/1
Authorization: Bearer <user-token>

HTTP/1.1 403 Forbidden
{"error": "Admin role required"}

리소스 없음 (404 Not Found)

GET /api/users/9999

HTTP/1.1 404 Not Found
{"error": "User not found"}

유효성 검사 실패 (422 Unprocessable Entity)

POST /api/users
{"name": "John", "email": "invalid-email"}

HTTP/1.1 422 Unprocessable Entity
{"errors": {"email": "Invalid email format"}}

5xx 서버 오류: 서버 측 문제

5xx 상태 코드는 서버의 예상치 못한 오류로 인해 요청을 처리할 수 없음을 나타냅니다. 클라이언트의 요청은 유효하지만 서버 측에서 문제가 발생했으므로, 클라이언트는 나중에 재시도해야 합니다.

코드이름원인
500Internal Server Error예상치 못한 서버 오류 — 예외 발생, 데이터베이스 연결 실패, 외부 API 오류 등
502Bad Gateway게이트웨이/프록시가 업스트림 서버에서 유효하지 않은 응답을 받음
503Service Unavailable서버 점검 중, 과부하 상태, 일시적 장애
504Gateway Timeout게이트웨이/프록시가 업스트림 서버로부터 시간 내에 응답을 받지 못함

실무 예시

예상치 못한 서버 오류 (500 Internal Server Error)

POST /api/users
Content-Type: application/json

{"name": "John", "email": "john@example.com"}

HTTP/1.1 500 Internal Server Error
{"error": "Database connection failed"}

서버 점검 중 (503 Service Unavailable)

GET /api/products

HTTP/1.1 503 Service Unavailable
Retry-After: 3600
{"error": "Service maintenance in progress. Please try again later."}

타임아웃 (504 Gateway Timeout)

GET /api/heavy-computation

HTTP/1.1 504 Gateway Timeout
{"error": "Request processing timeout"}

실무 가이드: 클라이언트 에러 핸들링

상태 코드별 클라이언트 처리 전략

프론트엔드 개발자는 상태 코드에 따라 다르게 대응해야 합니다:

async function fetchAPI(url, options) {
    const response = await fetch(url, options);
    
    if (response.ok) {  // 2xx
        return response.json();  // 데이터 사용
    }
    
    if (response.status === 401) {  // 인증 필요
        // 로그인 페이지로 리다이렉트
        window.location.href = '/login';
    }
    
    if (response.status === 403) {  // 권한 부족
        // 접근 불가 메시지 표시
        alert('접근 권한이 없습니다.');
    }
    
    if (response.status === 404) {  // 리소스 없음
        // 404 페이지 표시
        throw new NotFoundError('요청한 리소스가 없습니다.');
    }
    
    if (response.status === 422) {  // 유효성 검사 실패
        const errors = await response.json();
        // 사용자 입력 폼에 오류 메시지 표시
        displayValidationErrors(errors);
    }
    
    if (response.status >= 500) {  // 서버 오류
        // 재시도 로직 또는 사용자 안내
        throw new ServerError('일시적 오류 발생. 잠시 후 다시 시도해주세요.');
    }
}

RESTful API 설계 원칙

상태 코드는 HTTP 메서드와 리소스 상태를 함께 고려해서 결정합니다:

# 생성 성공
POST /api/users
201 Created
Location: /api/users/2
{
  "id": 2,
  "name": "Jane",
  "email": "jane@example.com"
}

# 조회 실패
GET /api/users/9999
404 Not Found

# 데이터 검증 실패
PATCH /api/users/1
422 Unprocessable Entity
{
  "errors": {
    "email": "Invalid email format",
    "age": "Must be at least 18"
  }
}

# 삭제 성공
DELETE /api/users/1
204 No Content

상태 코드 의사결정 플로우

API 응답 상태 코드를 결정할 때 다음 의사결정 트리를 따르세요:

요청 처리 성공?
├─ Yes
│  └─ 응답 본문 데이터 있나?
│     ├─ Yes → 200 OK
│     └─ No → 204 No Content
│
├─ No → 원인 파악
│  ├─ 인증 정보 부재 또는 만료? → 401 Unauthorized
│  ├─ 인증은 되었는데 권한 부족? → 403 Forbidden
│  ├─ 요청한 리소스가 없음? → 404 Not Found
│  ├─ 요청 형식 오류 또는 필수 파라미터 누락? → 400 Bad Request
│  ├─ 비즈니스 규칙 위반 (유효성 검사 실패)? → 422 Unprocessable Entity
│  ├─ 서버의 예상치 못한 오류? → 500 Internal Server Error
│  └─ 서버 점검 중 또는 일시적 장애? → 503 Service Unavailable

마치며

HTTP 상태 코드는 단순한 숫자가 아니라, 클라이언트와 서버 간의 명확한 계약입니다. 정확한 상태 코드 사용은:

  1. 개발자 경험 향상 — 클라이언트가 응답 본문을 파싱하지 않아도 결과를 이해할 수 있습니다.
  2. 버그 감소 — 명확한 상태 코드로 오류 원인을 빠르게 파악할 수 있습니다.
  3. 모니터링 개선 — 로그와 메트릭 분석이 수월해집니다.
  4. 팀 의사소통 — API 계약이 명확해져 리뷰와 통합이 수월합니다.

매번 API를 설계할 때, “사용자가 이 응답을 받으면 어떻게 행동할까?”를 생각하며 의도에 맞는 상태 코드를 선택하세요.

댓글