---
name: api-design-guide
description: RESTful API 설계 가이드 - 직관적이고 일관성 있는 API 만들기
---

# 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": "hong@example.com" }

# 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 구조

```json
// ✅ 성공 응답
{
  "data": {
    "id": "123",
    "name": "홍길동",
    "email": "hong@example.com"
  }
}

// ✅ 목록 응답
{
  "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": "유효한 이메일을 입력하세요"
      }
    ]
  }
}
```

### ❌ 피해야 할 패턴

```json
// ❌ 불일치한 구조
// 성공 시
{ "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

# 언제 사용?
# - 리소스가 독립적으로 관리될 때
# - 다양한 필터링이 필요할 때
```

---

## 에러 처리

### 명확한 에러 메시지

```json
// ✅ 좋은 에러 응답
{
  "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는 계약이다. 한 번 배포하면 쉽게 바꿀 수 없다."

- **일관성**: 같은 패턴을 반복하라
- **예측 가능성**: 놀라움을 주지 마라
- **명확성**: 문서 없이도 이해할 수 있게
- **하위 호환성**: 기존 클라이언트를 망가뜨리지 마라
- **단순함**: 복잡한 것보다 단순한 것이 낫다
