RESTful API 설계: 확장 가능한 API 구축을 위한 모범 사례

발행: (2026년 1월 4일 오후 10:41 GMT+9)
9 min read
원문: Dev.to

Source: Dev.to

번역할 텍스트를 제공해 주시면 한국어로 번역해 드리겠습니다.

리소스 명명

✅ 좋음 – 리소스는 명사

GET    /users               # List users
POST   /users               # Create user
GET    /users/123           # Get user
PUT    /users/123           # Update user
DELETE /users/123           # Delete user

❌ 나쁨 – URL에 동사 사용

GET    /getUsers
POST   /createUser
GET    /getUserById/123

관계를 위한 중첩 리소스

GET    /users/123/orders           # User's orders
GET    /users/123/orders/456       # Specific order
POST   /users/123/orders           # Create order for user

대안: 필터링을 위한 쿼리 파라미터

GET /orders?user_id=123   # Filter orders by user

경험 법칙: 컬렉션에는 복수형 명사(/users)를 사용하고, 특정 리소스에는 단수형 식별자(/users/123)를 사용합니다.

명명 규칙

  • Kebab‑case – 여러 단어로 이루어진 리소스

    GET /user-profiles
    GET /order-items
    GET /shipping-addresses
  • snake_case – 쿼리 파라미터

    GET /products?category_id=5&sort_by=price&sort_order=desc

HTTP 메서드

메서드목적멱등성?캐시 가능성?
GET리소스 조회
POST새로운 리소스 생성
PUT전체 리소스 교체
PATCH부분 업데이트
DELETE리소스 삭제
OPTIONS허용된 메서드 탐색
HEAD헤더만 조회

상태 코드

성공

200 OK               # 성공적인 GET, PUT, PATCH
201 Created          # 성공적인 POST (Location 헤더 포함)
204 No Content       # 성공적인 DELETE

클라이언트 오류

400 Bad Request          # 잘못된 요청 본문/매개변수
401 Unauthorized         # 누락되었거나 잘못된 인증
403 Forbidden           # 인증은 되었지만 권한이 없음
404 Not Found            # 리소스가 존재하지 않음
409 Conflict             # 리소스 충돌 (중복 등)
422 Unprocessable Entity # 검증 오류
429 Too Many Requests    # 속도 제한 초과

서버 오류

500 Internal Server Error   # 예상치 못한 서버 오류
502 Bad Gateway            # 상위 서비스 오류
503 Service Unavailable    # 일시적 과부하

Response Payloads

Single‑resource success

{
  "data": {
    "id": 123,
    "name": "John Doe",
    "email": "john@example.com",
    "created_at": "2024-01-15T10:30:00Z"
  },
  "meta": {
    "request_id": "req_abc123"
  }
}

Collection response with pagination

{
  "data": [
    { "id": 1, "name": "Product A" },
    { "id": 2, "name": "Product B" }
  ],
  "meta": {
    "total": 150,
    "page": 1,
    "per_page": 20,
    "total_pages": 8
  },
  "links": {
    "self": "/products?page=1",
    "next": "/products?page=2",
    "last": "/products?page=8"
  }
}

Error response

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request contains invalid data",
    "details": [
      { "field": "email", "message": "Invalid email format" },
      { "field": "age",   "message": "Must be at least 18" }
    ]
  },
  "meta": {
    "request_id": "req_xyz789"
  }
}

팁: 디버깅 및 지원을 위해 응답에 항상 request_id(또는 유사한 값)를 포함하세요.

쿼리 기반 기능

필터링

GET /products?category=electronics&brand=apple&min_price=100&max_price=500

정렬

GET /products?sort=price:asc,created_at:desc

페이지네이션

  • 오프셋 기반

    GET /products?page=2&per_page=20
  • 커서 기반 (대용량 데이터셋에 더 적합)

    GET /products?cursor=eyJpZCI6MTAwfQ&limit=20

희소 필드셋 (필드 선택)

GET /users/123?fields=id,name,email

연관 리소스 포함

GET /orders/123?include=user,items,shipping_address

버전 관리

GET /v1/users
GET /v2/users

헤더 기반 버전 관리 (권장)

GET /users
Accept: application/vnd.myapi.v2+json

폐기 헤더 (구버전용)

{
  "headers": {
    "Deprecation": "Sun, 01 Jan 2025 00:00:00 GMT",
    "Sunset": "Sun, 01 Jul 2025 00:00:00 GMT",
    "Link": "; rel=\"successor-version\""
  }
}

인증 및 권한 부여

베어러 토큰 (Authorization 헤더)

GET /api/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

JWT 페이로드 예시

{
  "header": { "alg": "RS256", "typ": "JWT" },
  "payload": {
    "sub": "user_123",
    "email": "user@example.com",
    "roles": ["user", "admin"],
    "iat": 1704067200,
    "exp": 1704153600
  }
}

API‑키 (헤더 – 권장)

GET /api/data
X-API-Key: sk_live_abc123xyz

API‑키 (쿼리 – 보안 수준 낮음)

GET /api/data?api_key=sk_live_abc123xyz

세분화된 권한 모델

  • read:users – 사용자 데이터 읽기
  • write:users – 사용자 생성 / 업데이트
  • delete:users – 사용자 삭제
  • read:orders – 주문 데이터 읽기
  • admin – 전체 접근 권한

스코프가 포함된 토큰

{
  "access_token": "...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read:users read:orders"
}

Rate Limiting

성공적인 응답 헤더

HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1704067200

속도 제한이 적용된 경우

HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1704067200

슬라이딩‑윈도우 구현 (예시)

const rateLimiter = {
  // Per‑user limits
  authenticated: {
    requests: 1000,
    window: '1 hour'
  },
  // Per‑IP limits for unauthenticated callers
  anonymous: {
    requests: 100,
    window: '1 hour'
  },
  // Per‑endpoint limits
  endpoints: {
    'POST /auth/login': { requests: 5,  window: '1 minute' },
    'POST /users':      { requests: 10, window: '1 hour' }
  }
};

구조화된 오류 카탈로그

{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "The requested user was not found",
    "details": {
      "resource_type": "User",
      "resource_id": "123"
    },
    "documentation_url": "https://api.example.com/docs/errors#RESOURCE_NOT_FOUND",
    "request_id": "req_abc123"
  }
}

중앙 집중식 오류‑코드 맵 (예시)

const errorCodes = {
  // Authentication errors (1xxx)
  INVALID_CREDENTIALS: { status: 401, message: 'Invalid email or password' },
  TOKEN_EXPIRED:      { status: 401, message: 'Authentication token has expired' },
  INSUFFICIENT_PERMISSIONS: { status: 403, message: 'Insufficient permissions' },

  // Validation errors (2xxx)
  VALIDATION_ERROR: { status: 422, message: 'Validation failed' },

  // Resource errors (3xxx)
  RESOURCE_NOT_FOUND: { status: 404, message: 'Resource not found' },
  CONFLICT:           { status: 409, message: 'Resource conflict' },

  // Rate‑limit errors (4xxx)
  TOO_MANY_REQUESTS: { status: 429, message: 'Rate limit exceeded' },

  // Server errors (5xxx)
  INTERNAL_ERROR: { status: 500, message: 'Internal server error' }
};

빠른 체크리스트

  • Resources: 명사는 컬렉션에 대해 복수형, 항목에 대해 단수형 ID 사용.
  • Naming: URL은 kebab‑case, 쿼리 파라미터는 snake_case 사용.
  • Methods: 올바른 HTTP 메서드 사용; 적절한 경우 멱등성을 유지.
  • Status codes: 상황에 가장 구체적인 코드를 반환.
  • Responses: 페이지네이션 시 data, meta(request_id 포함), links 포함.
  • Filtering / Sorting / Pagination: 쿼리 파라미터를 통해 노출.
  • Versioning: URL 또는 media‑type 버전 관리; 적절한 헤더로 폐기 알림.
  • Auth: 헤더 기반 토큰 또는 API 키 선호; 스코프/역할 포함.
  • Rate limiting: 응답 헤더를 통해 제한 전달; 슬라이딩 윈도우 구현.
  • Errors: 일관된 JSON 구조, 오류 코드, 문서 링크, 요청 ID 포함.

이 가이드라인을 따라 명확하고, 일관되며, 사용하기 쉬운 API를 구축하세요.

오류 정의

RMISSIONS: { status: 403, message: 'You do not have permission' },

// Validation errors (2xxx)
VALIDATION_ERROR: { status: 422, message: 'Request validation failed' },
INVALID_FORMAT: { status: 400, message: 'Invalid request format' },

// Resource errors (3xxx)
RESOURCE_NOT_FOUND: { status: 404, message: 'Resource not found' },
RESOURCE_CONFLICT: { status: 409, message: 'Resource already exists' },

// Rate limiting (4xxx)
RATE_LIMIT_EXCEEDED: { status: 429, message: 'Too many requests' },

// Server errors (5xxx)
INTERNAL_ERROR: { status: 500, message: 'An unexpected error occurred' },
SERVICE_UNAVAILABLE: { status: 503, message: 'Service temporarily unavailable' },
};

샘플 HAL‑스타일 응답

{
  "data": {
    "id": 123,
    "status": "pending",
    "total": 99.99
  },
  "links": {
    "self": { "href": "/orders/123" },
    "customer": { "href": "/users/456" },
    "items": { "href": "/orders/123/items" }
  },
  "actions": {
    "cancel": {
      "href": "/orders/123/cancel",
      "method": "POST",
      "title": "Cancel this order"
    },
    "pay": {
      "href": "/orders/123/pay",
      "method": "POST",
      "title": "Process payment"
    }
  }
}

OpenAPI 사양 (YAML)

openapi: 3.0.3
info:
  title: My API
  version: 1.0.0
  description: A sample API

paths:
  /users:
    get:
      summary: List all users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        email:
          type: string
          format: email

API 설계 모범 사례

잘 설계된 API는 직관적이고 일관되며 확장성이 뛰어납니다. 적절한 리소스 명명, 알맞은 상태 코드 사용, 포괄적인 오류 처리, 명확한 문서화를 포함한 이러한 모범 사례를 따르면 개발자들이 사용하기 좋아하는 API를 만들 수 있습니다.

핵심 요점

  • 리소스에는 명사를 사용하고, 동작에는 HTTP 메서드를 사용합니다.
  • 적절한 상태 코드를 반환합니다.
  • 처음부터 올바른 버전 관리를 구현합니다.
  • 포괄적인 오류 응답을 설계합니다.
  • 모든 것을 OpenAPI로 문서화합니다.
Back to Blog

관련 글

더 보기 »

2015년처럼 API를 작성하지 마세요

우리는 2025년에 살고 있으며, 많은 코드베이스가 여전히 API를 단순히 “JSON을 반환하는 엔드포인트”로만 취급합니다. API 설계가 기본 CRUD 라우트를 넘어 발전하지 않았다면, 당신은 sacr…