← 전체 글로 돌아가기

API

API 에러 응답을 일관되게 만들기

에러 응답 형식을 미리 정해두면 프론트엔드에서 처리하기 훨씬 쉬워진다.

API를 만들다 보면 정상 응답은 잘 만들지만, 에러 응답은 상황마다 제각각이 되기 쉽다. 프론트엔드에서는 에러 응답 형식을 예측할 수 없어서 복잡한 처리를 해야 한다.

핵심은 에러도 정상 응답만큼 체계적으로 설계해야 한다는 것이다.

에러 응답 형식 정의

먼저 어떤 에러가 발생할 수 있는지 생각하고, 각 에러마다 응답 형식을 정한다.

{
  "status": 400,
  "error": "VALIDATION_ERROR",
  "message": "입력값이 유효하지 않습니다",
  "details": {
    "field": "email",
    "reason": "invalid_format"
  }
}

HTTP 상태 코드 일관성

같은 종류의 에러는 같은 상태 코드를 사용한다. 예를 들어, 입력값 검증 실패는 항상 400, 권한 없음은 항상 403이어야 한다.

400 - Bad Request (입력값 검증 실패)
401 - Unauthorized (인증 필요)
403 - Forbidden (권한 없음)
404 - Not Found (리소스 없음)
500 - Internal Server Error (서버 에러)

에러 메시지 일관성

같은 에러는 같은 메시지 형식으로 응답한다. 에러 메시지가 계속 바뀌면 프론트에서 처리 로직을 예측할 수 없다.

# 좋은 예
{"error": "USER_NOT_FOUND"}

# 나쁜 예
{"error": "존재하지 않는 사용자입니다"}
{"message": "해당 유저가 없습니다"}

스택 트레이스는 프로덕션에서 숨기기

로컬에서는 디버깅을 위해 상세한 에러 정보가 필요하지만, 프로덕션에서는 민감한 정보를 노출하면 안 된다.

// 로컬 개발
if (process.env.NODE_ENV === 'development') {
  error.stack = stackTrace
}

// 프로덕션
return { error: 'INTERNAL_ERROR', message: '알 수 없는 에러가 발생했습니다' }

유효성 검사 에러의 상세 정보

입력값 검증이 실패하면, 어느 필드가 잘못됐는지 알려준다. 프론트엔드에서는 이 정보로 입력 필드에 에러 표시를 할 수 있다.

{
  "status": 400,
  "error": "VALIDATION_ERROR",
  "errors": [
    { "field": "email", "message": "유효한 이메일 형식이 아닙니다" },
    { "field": "password", "message": "최소 8자 이상이어야 합니다" }
  ]
}

에러 응답 테스트

배포 전에 각 에러 케이스가 제대로 응답하는지 테스트한다.

# 유효하지 않은 입력
curl -X POST https://api.example.com/users -d '{"email": "invalid"}'

# 존재하지 않는 리소스
curl https://api.example.com/users/99999

# 권한 없음
curl -H "Authorization: Bearer invalid_token" https://api.example.com/admin

프론트엔드 에러 처리

API 에러 응답 형식을 미리 정했으면, 프론트엔드도 같은 형식을 기대하고 처리한다.

fetch(url)
  .then(res => {
    if (!res.ok) throw new ApiError(res.status, res.body)
    return res.json()
  })
  .catch(err => {
    if (err.error === 'VALIDATION_ERROR') {
      showFieldErrors(err.errors)
    }
  })
  1. 가능한 모든 에러 케이스를 정리한다.
  2. 각 에러마다 일관된 상태 코드와 메시지 형식을 정한다.
  3. 배포 전에 각 에러 케이스를 테스트한다.
  4. 프론트엔드와 에러 응답 형식을 미리 공유한다.

마지막 확인

에러 응답을 설계한 후에는 실제 프론트엔드에서 그 에러를 제대로 처리할 수 있는지 확인해야 한다. 에러 메시지가 사용자에게 명확한지, 에러 상황에서 복구할 수 있는지까지 봐야 한다.