API
API 상태 코드 체크리스트
API 응답의 상태 코드가 의미하는 바를 정확히 이해하고 올바른 코드를 반환하는 방법.
API를 만들 때 상태 코드는 무시하기 쉽다. "일단 데이터만 반환하면 되지"라고 생각하면 클라이언트에서 응답을 제대로 처리할 수 없다. 상태 코드는 화면만 봐서는 모르는 정보를 전달한다.
문제의 경계 파악하기
같은 에러도 상황에 따라 다른 코드를 반환해야 한다.
// ❌ 잘못된 예: 모든 에러를 500으로 반환
app.post('/api/users', async (req, res) => {
try {
const user = await createUser(req.body);
res.json({ success: true, user });
} catch (e) {
res.status(500).json({ error: 'Server error' }); // 너무 많은 상황을 500으로
}
});
// ✅ 올바른 예: 상황에 맞는 상태 코드
app.post('/api/users', async (req, res) => {
// 요청 검증
if (!req.body.email) {
return res.status(400).json({ error: 'Email required' });
}
// 인증
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
// 권한
if (req.user.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
try {
// 이미 존재하는 데이터
const existing = await User.findOne({ email: req.body.email });
if (existing) {
return res.status(409).json({ error: 'Email already exists' });
}
const user = await User.create(req.body);
res.status(201).json({ success: true, user }); // 생성 성공
} catch (e) {
res.status(500).json({ error: 'Server error' });
}
});
로컬과 운영 비교
API의 응답은 환경에 따라 다를 수 있다.
# 로컬 환경 테스트
curl -i http://localhost:3000/api/users -X POST \
-H 'Content-Type: application/json' \
-d '{"email": "[email protected]"}'
# 배포 환경 테스트
curl -i https://example.com/api/users -X POST \
-H 'Content-Type: application/json' \
-d '{"email": "[email protected]"}'
같은 요청인데 다른 상태 코드가 나오면, 환경 설정이 다른 것이다.
이벤트 흐름으로 상태 코드 선택
사용자 흐름을 따라가며 각 단계에서 나올 수 있는 상태 코드를 정의한다.
성공 케이스
200 OK - 기존 자원 조회/수정/삭제
201 Created - 새 자원 생성
204 No Content - 응답 본문 없음
클라이언트 에러 (4xx)
400 Bad Request - 요청 형식 잘못됨
401 Unauthorized - 인증 필요
403 Forbidden - 권한 없음
404 Not Found - 자원 없음
409 Conflict - 중복/충돌
429 Too Many Requests - 요청 너무 많음
서버 에러 (5xx)
500 Internal Server Error - 예상 못 한 에러
502 Bad Gateway - 업스트림 서버 응답 없음
503 Service Unavailable - 서버 유지보수 중
응답 설계
상태 코드만으로는 부족하다. 응답 본문도 명확해야 한다.
// 200 OK - 성공
{
"success": true,
"data": { "id": 1, "name": "John" }
}
// 400 Bad Request - 입력 검증 실패
{
"success": false,
"error": "Validation failed",
"details": {
"email": "Invalid email format",
"age": "Must be at least 18"
}
}
// 401 Unauthorized - 인증 필요
{
"success": false,
"error": "Authentication required"
}
// 403 Forbidden - 권한 없음
{
"success": false,
"error": "Insufficient permissions",
"required": "admin"
}
클라이언트에서 상태 코드 처리
프론트엔드에서는 상태 코드로 사용자 경험을 결정한다.
fetch('/api/users', { method: 'POST', body })
.then(res => {
if (res.status === 201) {
// 생성 성공
return res.json();
} else if (res.status === 400) {
// 입력 검증 실패 - 폼 에러 표시
throw new ValidationError(res.json());
} else if (res.status === 401) {
// 로그인 필요
window.location.href = '/login';
} else if (res.status === 403) {
// 권한 없음
throw new PermissionError('관리자만 가능합니다');
} else if (res.status === 409) {
// 중복 - 특정 메시지
throw new ConflictError('이미 존재하는 이메일입니다');
} else if (res.status >= 500) {
// 서버 에러 - 재시도 유도
throw new ServerError('잠시 후 다시 시도해주세요');
}
})
.catch(error => {
if (error instanceof ValidationError) {
// 필드별 에러 표시
displayFieldErrors(error.details);
} else if (error instanceof ServerError) {
// 사용자에게 알림
showToast(error.message);
}
});
측정과 기록
API 응답의 상태 코드 분포를 모니터링한다.
# 로그에서 상태 코드별 집계
grep 'POST /api/users' app.log | awk '{print $NF}' | sort | uniq -c
만약 4xx 에러가 많다면, API 문서나 입력 검증이 부족한 것일 수도 있다.
체크리스트
- 각 엔드포인트의 성공/실패 시나리오 정의
- 각 시나리오에 맞는 상태 코드 선택
- 응답 본문에 충분한 정보 포함 (에러 메시지, 필드별 에러 등)
- 클라이언트에서 상태 코드별 처리 로직 구현
- 로컬과 배포 환경에서 테스트
- 상태 코드 분포 모니터링
상태 코드는 API의 언어다. 정확한 상태 코드는 클라이언트 개발자의 시간을 절약하고, 사용자 경험도 개선한다.