API
API 응답 형식이 예기치 않게 변했을 때
응답 스키마 변경으로 인한 클라이언트 에러를 빠르게 추적하고 대응하기.
API를 운영하다 보면 응답 형식이 깨지는 경험을 한다. 어제까지 잘 작동하던 앱이 오늘 갑자기 "데이터 에러"라고 한다.
상태 코드부터 확인
curl -i 'https://api.example.com/api/items?page=1'
응답을 자세히 보면 3가지 정보가 있다.
- 상태 코드 (200, 400, 500 등)
- 응답 헤더 (Content-Type 등)
- 응답 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
변경이 있을 때마다 기록해두면, 문제가 생겼을 때 "언제부터"를 빠르게 파악할 수 있다.
체크리스트
- 상태 코드 확인
- 응답 JSON 유효성 확인
- 필드 추가/제거/타입 변경 확인
- 로컬과 운영 응답 비교
- 클라이언트 검증 코드 추가
- 변경 사항 기록
API 응답 형식이 바뀐 건 보통 의도된 변경이다. 다만 클라이언트까지 동시에 업데이트되지 않아서 문제가 생기는 경우가 많다.