SKILL.md
API 설계 가이드
핵심 원칙
1. 명확하고 예측 가능하게
API를 처음 보는 개발자가 문서 없이도 80% 이해할 수 있어야 합니다.
2. 일관성이 최우선
좋은 규칙을 일관되게 따르는 것이, 완벽한 규칙을 불규칙하게 따르는 것보다 낫습니다.
3. 개발자 경험(DX) 중심
API는 개발자를 위한 제품입니다. 사용하기 쉬워야 합니다.
URL 설계 원칙
✅ 좋은 URL 설계
# 명사 사용 (동사 X)
GET /users # ✅
GET /getUsers # ❌
# 복수형 사용
GET /users # ✅
GET /user # ❌
# 계층 구조 표현
GET /users/123/orders # ✅
GET /orders?userId=123 # 😐 (대안으로 허용)
# 케밥 케이스 사용
GET /user-profiles # ✅
GET /userProfiles # ❌
GET /user_profiles # ❌
# 필터는 쿼리 파라미터로
GET /products?category=electronics&sort=price # ✅
GET /products/electronics/sortByPrice # ❌
❌ 피해야 할 패턴
# URL에 동사 포함
POST /users/create # ❌
POST /users # ✅
# URL에 동작 표현
GET /users/search # ❌
GET /users?q=keyword # ✅
# 파일 확장자 포함
GET /users.json # ❌
GET /users # ✅ (Accept 헤더 사용)
HTTP 메서드 사용법
CRUD 매핑
# Create - POST
POST /users # 새 사용자 생성
Body: { "name": "홍길동", "email": "[email protected]" }
# Read - GET
GET /users # 목록 조회
GET /users/123 # 단건 조회
# Update - PUT/PATCH
PUT /users/123 # 전체 수정 (모든 필드 필요)
PATCH /users/123 # 부분 수정 (일부 필드만)
Body: { "name": "김철수" }
# Delete - DELETE
DELETE /users/123 # 삭제
멱등성(Idempotency) 이해하기
# 멱등성 O (여러 번 호출해도 같은 결과)
GET /users/123 # ✅ 항상 같은 결과
PUT /users/123 # ✅ 같은 데이터로 여러 번 호출해도 같은 상태
DELETE /users/123 # ✅ 두 번째부터는 404지만 상태는 같음
# 멱등성 X (호출할 때마다 결과가 다름)
POST /users # ❌ 호출할 때마다 새 사용자 생성
PATCH /orders/123/add-item # ❌ 호출할 때마다 아이템 추가
상태 코드 전략
자주 사용하는 코드만 정확히
# 성공 (2xx)
200 OK # GET, PUT, PATCH 성공
201 Created # POST 성공 (Location 헤더와 함께)
204 No Content # DELETE 성공 (응답 body 없음)
# 클라이언트 오류 (4xx)
400 Bad Request # 잘못된 요청 (validation 실패)
401 Unauthorized # 인증 필요
403 Forbidden # 권한 없음
404 Not Found # 리소스 없음
409 Conflict # 중복 등 충돌
# 서버 오류 (5xx)
500 Internal Server Error # 서버 오류
503 Service Unavailable # 일시적 서비스 불가
❌ 과도한 상태 코드 사용 피하기
# 불필요한 세분화
451 Unavailable For Legal Reasons # ❌ 너무 구체적
403 Forbidden # ✅ 충분함
# 일관성 없는 사용
POST /users → 200 OK (이미 있음) # ❌
POST /users → 409 Conflict # ✅
응답 형식
일관된 JSON 구조
// ✅ 성공 응답
{
"data": {
"id": "123",
"name": "홍길동",
"email": "[email protected]"
}
}
// ✅ 목록 응답
{
"data": [
{ "id": "1", "name": "홍길동" },
{ "id": "2", "name": "김철수" }
],
"pagination": {
"page": 1,
"pageSize": 20,
"totalCount": 100,
"totalPages": 5
}
}
// ✅ 에러 응답
{
"error": {
"code": "VALIDATION_ERROR",
"message": "이메일 형식이 올바르지 않습니다",
"details": [
{
"field": "email",
"message": "유효한 이메일을 입력하세요"
}
]
}
}
❌ 피해야 할 패턴
// ❌ 불일치한 구조
// 성공 시
{ "user": { "id": 1 } }
// 실패 시
{ "error": "..." }
// ✅ 일관된 구조
// 항상 data 또는 error 키 사용
페이지네이션
오프셋 기반 (간단한 경우)
GET /users?page=2&pageSize=20
Response:
{
"data": [...],
"pagination": {
"page": 2,
"pageSize": 20,
"totalCount": 150,
"totalPages": 8,
"hasNext": true,
"hasPrev": true
}
}
커서 기반 (대용량/실시간)
GET /posts?cursor=eyJpZCI6MTIzfQ&limit=20
Response:
{
"data": [...],
"pagination": {
"nextCursor": "eyJpZCI6MTQzfQ",
"prevCursor": "eyJpZCI6MTAzfQ",
"hasMore": true
}
}
필터링 & 정렬
쿼리 파라미터 설계
# 필터링
GET /products?category=electronics&minPrice=10000&maxPrice=50000
# 정렬
GET /products?sort=price # 오름차순
GET /products?sort=-price # 내림차순 (- 접두사)
GET /products?sort=price,-rating # 다중 정렬
# 필드 선택 (Sparse Fieldsets)
GET /users?fields=id,name,email # 필요한 필드만
# 검색
GET /products?q=노트북 # 통합 검색
GET /products?search=name:노트북 # 필드별 검색
인증 & 보안
Bearer Token 사용
# 헤더에 토큰 전달
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# ❌ URL에 토큰 포함하지 않기
GET /users?token=xxx # 위험! 로그에 남음
API 키 (공개 API)
# 헤더 사용 (권장)
X-API-Key: your-api-key-here
# 또는 쿼리 파라미터 (읽기 전용 API만)
GET /public/data?apiKey=xxx
버전 관리
URL 버전 (가장 명확)
GET /v1/users
GET /v2/users
# 장점: 명확하고 캐싱 쉬움
# 단점: URL이 길어짐
헤더 버전 (RESTful)
GET /users
Accept: application/vnd.myapp.v2+json
# 장점: URL 깔끔
# 단점: 브라우저 테스트 어려움
⚠️ 버전 변경 원칙
# 하위 호환성 유지
v1: { "name": "홍길동" }
v2: { "name": "홍길동", "firstName": "길동", "lastName": "홍" } # ✅
v1: { "name": "홍길동" }
v2: { "fullName": "홍길동" } # ❌ name 필드 제거 (breaking change)
관계 표현
중첩 리소스
# 사용자의 주문 조회
GET /users/123/orders
# 특정 주문 상세
GET /users/123/orders/456
독립 리소스 + 필터
# 모든 주문에서 필터링
GET /orders?userId=123
# 언제 사용?
# - 리소스가 독립적으로 관리될 때
# - 다양한 필터링이 필요할 때
에러 처리
명확한 에러 메시지
// ✅ 좋은 에러 응답
{
"error": {
"code": "VALIDATION_ERROR",
"message": "입력값을 확인해주세요",
"details": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "올바른 이메일 형식이 아닙니다"
},
{
"field": "password",
"code": "TOO_SHORT",
"message": "비밀번호는 8자 이상이어야 합니다"
}
],
"requestId": "req_123abc"
}
}
// ❌ 나쁜 에러 응답
{
"error": "Error" // 너무 모호함
}
에러 코드 체계
# 명명 규칙
VALIDATION_ERROR
RESOURCE_NOT_FOUND
AUTHENTICATION_REQUIRED
INSUFFICIENT_PERMISSIONS
RATE_LIMIT_EXCEEDED
# ❌ 피할 것
ERR_001 # 의미 불명확
error # 너무 일반적
Rate Limiting
헤더로 정보 제공
HTTP/1.1 200 OK
X-RateLimit-Limit: 100 # 시간당 한도
X-RateLimit-Remaining: 87 # 남은 요청 수
X-RateLimit-Reset: 1640000000 # 리셋 시각 (Unix timestamp)
# 한도 초과 시
HTTP/1.1 429 Too Many Requests
Retry-After: 3600 # 초 단위
실전 체크리스트
API 설계 전 확인
- URL이 명사로만 구성되어 있는가?
- 복수형을 일관되게 사용하는가?
- HTTP 메서드를 올바르게 사용하는가?
- 상태 코드가 의미에 맞는가?
- 응답 구조가 일관적인가?
- 에러 메시지가 명확한가?
- 페이지네이션이 구현되어 있는가?
- 인증 방식이 안전한가?
- 버전 관리 전략이 있는가?
- Rate limiting이 구현되어 있는가?
문서화 필수 항목
- 모든 엔드포인트 목록
- 요청/응답 예시
- 에러 코드 설명
- 인증 방법
- Rate limit 정책
나쁜 예 vs 좋은 예
케이스 1: 사용자 관리
# ❌ 나쁜 설계
GET /getUser?id=123
POST /createUser
POST /updateUser
POST /deleteUser
# ✅ 좋은 설계
GET /users/123
POST /users
PUT /users/123
DELETE /users/123
케이스 2: 검색
# ❌ 나쁜 설계
GET /search/users/홍길동
# ✅ 좋은 설계
GET /users?q=홍길동
케이스 3: 복잡한 작업
# ❌ 나쁜 설계
POST /orders/123/sendEmail
# ✅ 좋은 설계
POST /orders/123/notifications
Body: { "type": "email" }
# 또는 이벤트 기반
POST /orders/123/confirm # 확인하면 자동으로 이메일 발송
한국 서비스 특화
한글 지원
# URL 인코딩
GET /products?q=%ED%99%8D%EA%B8%B8%EB%8F%99
# Accept-Language 헤더
GET /products
Accept-Language: ko-KR
# 응답에 언어 포함
{
"data": {
"name": "상품명",
"nameEn": "Product Name"
}
}
주민등록번호 등 민감정보
# ❌ URL에 포함하지 않기
GET /users?residentNumber=123456-1234567
# ✅ POST body에 포함
POST /users/verify
Body: { "residentNumber": "123456-1234567" }
# 응답에서 마스킹
{
"residentNumber": "123456-1******"
}
마무리 원칙
"API는 계약이다. 한 번 배포하면 쉽게 바꿀 수 없다."
- 일관성: 같은 패턴을 반복하라
- 예측 가능성: 놀라움을 주지 마라
- 명확성: 문서 없이도 이해할 수 있게
- 하위 호환성: 기존 클라이언트를 망가뜨리지 마라
- 단순함: 복잡한 것보다 단순한 것이 낫다