← 전체 글로 돌아가기

API

API 응답 형식이 예기치 않게 변했을 때

응답 스키마 변경으로 인한 클라이언트 에러를 빠르게 추적하고 대응하기.

API를 운영하다 보면 응답 형식이 깨지는 경험을 한다. 어제까지 잘 작동하던 앱이 오늘 갑자기 "데이터 에러"라고 한다.

상태 코드부터 확인

curl -i 'https://api.example.com/api/items?page=1'

응답을 자세히 보면 3가지 정보가 있다.

  1. 상태 코드 (200, 400, 500 등)
  2. 응답 헤더 (Content-Type 등)
  3. 응답 Body

각각 다르게 의미한다.

상태 코드별 의미

200 (OK)

{
  "data": [...],
  "meta": { "total": 100 }
}

상태는 OK인데 body 형식이 틀렸을 수도 있다.

400 (Bad Request)

{
  "error": "invalid_parameter",
  "message": "page must be a number"
}

요청이 잘못됐다는 뜻. 파라미터를 확인해야 한다.

500 (Server Error)

서버가 처리하지 못했다. 서버 로그를 봐야 한다.

응답 Body의 형식 확인

# JSON 형식이 유효한가?
curl -s 'https://api.example.com/api/items' | jq '.'
# jq가 없으면 JSON 파싱 실패

# 특정 필드가 있나?
curl -s 'https://api.example.com/api/items' | jq '.data | length'

로컬과 운영의 응답 비교

# 로컬
curl -s 'http://localhost:3000/api/items' | jq '.'

# 운영
curl -s 'https://api.example.com/api/items' | jq '.'

# 차이 확인
curl -s 'http://localhost:3000/api/items' > local.json
curl -s 'https://api.example.com/api/items' > prod.json
diff local.json prod.json

형식이 다르면 diff에서 즉시 보인다.

응답 형식의 문제들

1. 필드 추가됨

// 예전
interface Item {
  id: string;
  name: string;
}

// 새로움 (필드 추가)
interface Item {
  id: string;
  name: string;
  description: string;  // 새로 추가됨
}

JavaScript에서는 추가 필드가 있어도 보통 작동한다. 하지만 타입스크립트면 에러가 난다.

2. 필드가 없어짐

// 예전
interface Item {
  id: string;
  name: string;
  description: string;
}

// 새로움 (필드 제거)
interface Item {
  id: string;
  name: string;
  // description은 이제 없음
}

이 경우가 더 위험하다. 코드에서 item.description을 쓰면 undefined를 얻는다.

3. 필드 타입 변경

// 예전
interface Item {
  price: number;  // 숫자
}

// 새로움
interface Item {
  price: string;  // 문자열 ("10.99")
}

API가 응답을 JSON으로 직렬화하면서 타입이 바뀔 수 있다.

4. 배열/객체 구조 변경

// 예전
interface Response {
  data: Item[];  // 배열
}

// 새로움
interface Response {
  data: {        // 객체로 변경
    items: Item[];
    total: number;
  };
}

가장 해결하기 어렵다. 코드를 다시 써야 한다.

빠르게 원인을 찾기

# 1. 실제 응답이 뭔가?
curl -s 'https://api.example.com/api/items?page=1' | jq '.' | head -20

# 2. 클라이언트가 기대하는 형식
cat src/types/api.ts | grep -A 10 'interface.*Items'

# 3. 매칭되나?
# 수동으로 비교

클라이언트 검증 추가

interface Item {
  id: string;
  name: string;
  price?: number;  // optional으로 만들기
}

function parseResponse(data: unknown): Item[] {
  if (!Array.isArray(data)) {
    throw new Error('Expected array');
  }

  return data.map(item => {
    if (!item.id || !item.name) {
      throw new Error('Invalid item structure');
    }
    return item as Item;
  });
}

응답을 곧바로 쓰지 말고 검증하는 코드를 거친다.

버전 관리

# API 버전을 URL에 포함
https://api.example.com/v1/items  # 예전
https://api.example.com/v2/items  # 새로움

또는 헤더로:

curl -H 'API-Version: 2' https://api.example.com/items

응답 변경 로그

# 언제 무엇이 바뀌었나?
echo "2024-06-30: Added 'description' field to Item" >> API_CHANGELOG.md
echo "2024-06-30: Renamed 'meta.count' to 'meta.total'" >> API_CHANGELOG.md

변경이 있을 때마다 기록해두면, 문제가 생겼을 때 "언제부터"를 빠르게 파악할 수 있다.

체크리스트

  1. 상태 코드 확인
  2. 응답 JSON 유효성 확인
  3. 필드 추가/제거/타입 변경 확인
  4. 로컬과 운영 응답 비교
  5. 클라이언트 검증 코드 추가
  6. 변경 사항 기록

API 응답 형식이 바뀐 건 보통 의도된 변경이다. 다만 클라이언트까지 동시에 업데이트되지 않아서 문제가 생기는 경우가 많다.