smithery/gdm0714

api-design-guide

RESTful API 설계 가이드 - 직관적이고 일관성 있는 API 만들기

Installation

$ npx skills add smithery/gdm0714 --skill api-design-guide

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/gdm0714.

npx skills add smithery/gdm0714

Browse all from smithery/gdm0714

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 10,258 B
  • docs SUMMARY.md 101 B

History

  1. First recorded snapshot · 0 installs

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는 계약이다. 한 번 배포하면 쉽게 바꿀 수 없다."

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