Communitygithub.com

api-design

프로덕션 API를 위한 REST API 디자인 패턴: 리소스 명명, 상태 코드, 페이지네이션, 필터링, 오류 응답, 버전 관리, 속도 제한. REST 엔드포인트, 리소스 이름, 상태 코드, 페이지네이션 또는 버전 관리를 설계하거나 검토할 때 사용하세요.

api-design란 무엇인가요?

이 스킬은 일관되고 개발자 친화적인 REST API를 설계하기 위한 종합적인 가이드입니다. URL 구조, HTTP 메서드와 상태 코드의 올바른 사용법, 성공 및 오류 응답 형식, 페이지네이션(오프셋 기반 및 커서 기반), 필터링/정렬/검색, 인증 및 권한 부여, 속도 제한, API 버전 관리 전략을 다룹니다. 실제 구현 예제는 TypeScript(Next.js), Python(Django REST Framework), Go(net/http)로 제공되며, 엔드포인트 출시 전 확인해야 할 항목이 포함된 체크리스트도 포함되어 있습니다. 이 스킬은 프로덕션 환경의 공개 API 또는 파트너용 API를 구축하거나 검토하는 모든 개발자에게 유용하며, API 디자인 시 흔히 발생하는 실수를 피하고 모범 사례를 적용하도록 돕습니다.

지원 대상~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/api-design

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

API 디자인 패턴

일관되고 개발자 친화적인 REST API를 설계하기 위한 규칙과 모범 사례입니다.

활성화 시점

  • 새로운 API 엔드포인트 설계
  • 기존 API 계약 검토
  • 페이지네이션, 필터링 또는 정렬 추가
  • API 오류 처리 구현
  • API 버전 관리 전략 수립
  • 공개 또는 파트너용 API 구축

리소스 디자인

URL 구조

# 리소스는 명사, 복수형, 소문자, 케밥-케이스 사용
GET    /api/v1/users
GET    /api/v1/users/:id
POST   /api/v1/users
PUT    /api/v1/users/:id
PATCH  /api/v1/users/:id
DELETE /api/v1/users/:id

# 관계에 대한 하위 리소스
GET    /api/v1/users/:id/orders
POST   /api/v1/users/:id/orders

# CRUD에 매핑되지 않는 액션 (동사는 자제)
POST   /api/v1/orders/:id/cancel
POST   /api/v1/auth/login
POST   /api/v1/auth/refresh

명명 규칙

# 좋음
/api/v1/team-members          # 여러 단어 리소스는 케밥-케이스
/api/v1/orders?status=active  # 필터링은 쿼리 파라미터 사용
/api/v1/users/123/orders      # 소유권은 중첩 리소스 사용

# 나쁨
/api/v1/getUsers              # URL에 동사 포함
/api/v1/user                  # 단수형 (복수형 사용)
/api/v1/team_members          # URL에 스네이크_케이스 사용
/api/v1/users/123/getOrders   # 중첩 리소스에 동사 포함

HTTP 메서드 및 상태 코드

메서드 의미

메서드멱등성안전함사용처
GET예예리소스 조회
POST아니오아니오리소스 생성, 액션 트리거
PUT예아니오리소스 전체 교체
PATCH아니오*아니오리소스 부분 업데이트
DELETE예아니오리소스 제거

*PATCH는 적절히 구현하면 멱등성을 가질 수 있음

상태 코드 참조

# 성공
200 OK                    — GET, PUT, PATCH (응답 본문 포함)
201 Created               — POST (Location 헤더 포함)
204 No Content            — DELETE, PUT (응답 본문 없음)

# 클라이언트 오류
400 Bad Request           — 유효성 검사 실패, 잘못된 JSON 형식
401 Unauthorized          — 인증 누락 또는 유효하지 않음
403 Forbidden             — 인증되었지만 권한 없음
404 Not Found             — 리소스 없음
409 Conflict              — 중복 항목, 상태 충돌
422 Unprocessable Entity  — 의미적으로 유효하지 않음 (JSON은 유효하나 데이터가 잘못됨)
429 Too Many Requests     — 속도 제한 초과

# 서버 오류
500 Internal Server Error — 예상치 못한 실패 (세부 정보 절대 노출 금지)
502 Bad Gateway           — 업스트림 서비스 실패
503 Service Unavailable   — 일시적 과부하, Retry-After 헤더 포함

일반적인 실수

# 나쁨: 모든 응답에 200 사용
{ "status": 200, "success": false, "error": "Not found" }

# 좋음: HTTP 상태 코드를 의미적으로 사용
HTTP/1.1 404 Not Found
{ "error": { "code": "not_found", "message": "User not found" } }

# 나쁨: 유효성 검사 오류에 500 사용
# 좋음: 필드 수준 세부 정보와 함께 400 또는 422 사용

# 나쁨: 생성된 리소스에 200 사용
# 좋음: Location 헤더와 함께 201 사용
HTTP/1.1 201 Created
Location: /api/v1/users/abc-123

응답 형식

성공 응답

{
  "data": {
    "id": "abc-123",
    "email": "[email protected]",
    "name": "Alice",
    "created_at": "2025-01-15T10:30:00Z"
  }
}

컬렉션 응답 (페이지네이션 포함)

{
  "data": [
    { "id": "abc-123", "name": "Alice" },
    { "id": "def-456", "name": "Bob" }
  ],
  "meta": {
    "total": 142,
    "page": 1,
    "per_page": 20,
    "total_pages": 8
  },
  "links": {
    "self": "/api/v1/users?page=1&per_page=20",
    "next": "/api/v1/users?page=2&per_page=20",
    "last": "/api/v1/users?page=8&per_page=20"
  }
}

오류 응답

{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed",
    "details": [
      {
        "field": "email",
        "message": "Must be a valid email address",
        "code": "invalid_format"
      },
      {
        "field": "age",
        "message": "Must be between 0 and 150",
        "code": "out_of_range"
      }
    ]
  }
}

응답 래퍼 변형

// 옵션 A: 데이터 래퍼를 포함한 래퍼 (공개 API 권장)
interface ApiResponse<T> {
  data: T;
  meta?: PaginationMeta;
  links?: PaginationLinks;
}

interface ApiError {
  error: {
    code: string;
    message: string;
    details?: FieldError[];
  };
}

// 옵션 B: 플랫 응답 (더 간단, 내부 API에 흔함)
// 성공: 리소스를 직접 반환
// 오류: 오류 객체 반환
// HTTP 상태 코드로 구분

페이지네이션

오프셋 기반 (간단)

GET /api/v1/users?page=2&per_page=20

# 구현
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 20 OFFSET 20;

장점: 구현 쉬움, "N페이지로 이동" 지원 단점: 큰 오프셋에서 느림 (OFFSET 100000), 동시 삽입 시 일관성 없음

커서 기반 (확장 가능)

GET /api/v1/users?cursor=eyJpZCI6MTIzfQ&limit=20

# 구현
SELECT * FROM users
WHERE id > :cursor_id
ORDER BY id ASC
LIMIT 21;  -- has_next 결정을 위해 하나 더 가져옴
{
  "data": [...],
  "meta": {
    "has_next": true,
    "next_cursor": "eyJpZCI6MTQzfQ"
  }
}

장점: 위치에 관계없이 일관된 성능, 동시 삽입에도 안정적 단점: 임의 페이지로 이동 불가, 커서는 불투명함

각각 사용하는 경우

사용 사례페이지네이션 유형
관리자 대시보드, 소규모 데이터셋 (<10K)오프셋
무한 스크롤, 피드, 대규모 데이터셋커서
공개 API커서 (기본값) 및 오프셋 (선택 사항)
검색 결과오프셋 (사용자가 페이지 번호 기대)

필터링, 정렬 및 검색

필터링

# 단순 일치
GET /api/v1/orders?status=active&customer_id=abc-123

# 비교 연산자 (대괄호 표기법 사용)
GET /api/v1/products?price[gte]=10&price[lte]=100
GET /api/v1/orders?created_at[after]=2025-01-01

# 다중 값 (쉼표로 구분)
GET /api/v1/products?category=electronics,clothing

# 중첩 필드 (점 표기법)
GET /api/v1/orders?customer.country=US

정렬

# 단일 필드 (내림차순은 - 접두사)
GET /api/v1/products?sort=-created_at

# 다중 필드 (쉼표로 구분)
GET /api/v1/products?sort=-featured,price,-created_at

전체 텍스트 검색

# 검색 쿼리 파라미터
GET /api/v1/products?q=wireless+headphones

# 필드별 검색
GET /api/v1/users?email=alice

필드 선택 (Sparse Fieldsets)

# 지정된 필드만 반환 (페이로드 감소)
GET /api/v1/users?fields=id,name,email
GET /api/v1/orders?fields=id,total,status&include=customer.name

인증 및 권한 부여

토큰 기반 인증

# Authorization 헤더의 Bearer 토큰
GET /api/v1/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

# API 키 (서버 간 통신용)
GET /api/v1/data
X-API-Key: sk_live_abc123

권한 부여 패턴

// 리소스 수준: 소유권 확인
app.get("/api/v1/orders/:id", async (req, res) => {
  const order = await Order.findById(req.params.id);
  if (!order) return res.status(404).json({ error: { code: "not_found" } });
  if (order.userId !== req.user.id) return res.status(403).json({ error: { code: "forbidden" } });
  return res.json({ data: order });
});

// 역할 기반: 권한 확인
app.delete("/api/v1/users/:id", requireRole("admin"), async (req, res) => {
  await User.delete(req.params.id);
  return res.status(204).send();
});

속도 제한

헤더

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640000000

# 초과 시
HTTP/1.1 429 Too Many Requests
Retry-After: 60
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Try again in 60 seconds."
  }
}

속도 제한 계층

계층제한시간 창사용 사례
익명30/분IP 기준공개 엔드포인트
인증됨100/분사용자 기준표준 API 액세스
프리미엄1000/분API 키 기준유료 API 요금제
내부10000/분서비스 기준서비스 간 통신

버전 관리

URL 경로 버전 관리 (권장)

/api/v1/users
/api/v2/users

장점: 명시적, 라우팅 쉬움, 캐시 가능 단점: 버전 간 URL 변경

헤더 버전 관리

GET /api/users
Accept: application/vnd.myapp.v2+json

장점: 깔끔한 URL 단점: 테스트 어려움, 잊기 쉬움

버전 관리 전략

1. /api/v1/으로 시작 — 필요할 때까지 버전 관리하지 않음
2. 최대 2개의 활성 버전 유지 (현재 + 이전)
3. 지원 중단 일정:
   - 지원 중단 공지 (공개 API의 경우 6개월 전)
   - Sunset 헤더 추가: Sunset: Sat, 01 Jan 2026 00:00:00 GMT
   - 지원 중단 날짜 이후에는 410 Gone 반환
4. 비호환 변경은 새 버전이 필요하지 않음:
   - 응답에 새 필드 추가
   - 새로운 선택적 쿼리 파라미터 추가
   - 새 엔드포인트 추가
5. 호환성 깨는 변경은 새 버전 필요:
   - 필드 제거 또는 이름 변경
   - 필드 유형 변경
   - URL 구조 변경
   - 인증 방법 변경

구현 패턴

TypeScript (Next.js API Route)

import { z } from "zod";
import { NextRequest, NextResponse } from "next/server";

const createUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
});

export async function POST(req: NextRequest) {
  const body = await req.json();
  const parsed = createUserSchema.safeParse(body);

  if (!parsed.success) {
    return NextResponse.json({
      error: {
        code: "validation_error",
        message: "Request validation failed",
        details: parsed.error.issues.map(i => ({
          field: i.path.join("."),
          message: i.message,
          code: i.code,
        })),
      },
    }, { status: 422 });
  }

  const user = await createUser(parsed.data);

  return NextResponse.json(
    { data: user },
    {
      status: 201,
      headers: { Location: `/api/v1/users/${user.id}` },
    },
  );
}

Python (Django REST Framework)

from rest_framework import serializers, viewsets, status
from rest_framework.response import Response

class CreateUserSerializer(serializers.Serializer):
    email = serializers.EmailField()
    name = serializers.CharField(max_length=100)

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ["id", "email", "name", "created_at"]

class UserViewSet(viewsets.ModelViewSet):
    serializer_class = UserSerializer
    permission_classes = [IsAuthenticated]

    def get_serializer_class(self):
        if self.action == "create":
            return CreateUserSerializer
        return UserSerializer

    def create(self, request):
        serializer = CreateUserSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = UserService.create(**serializer.validated_data)
        return Response(
            {"data": UserSerializer(user).data},
            status=status.HTTP_201_CREATED,
            headers={"Location": f"/api/v1/users/{user.id}"},
        )

Go (net/http)

func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
    var req CreateUserRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        writeError(w, http.StatusBadRequest, "invalid_json", "Invalid request body")
        return
    }

    if err := req.Validate(); err != nil {
        writeError(w, http.StatusUnprocessableEntity, "validation_error", err.Error())
        return
    }

    user, err := h.service.Create(r.Context(), req)
    if err != nil {
        switch {
        case errors.Is(err, domain.ErrEmailTaken):
            writeError(w, http.StatusConflict, "email_taken", "Email already registered")
        default:
            writeError(w, http.StatusInternalServerError, "internal_error", "Internal error")
        }
        return
    }

    w.Header().Set("Location", fmt.Sprintf("/api/v1/users/%s", user.ID))
    writeJSON(w, http.StatusCreated, map[string]any{"data": user})
}

API 디자인 체크리스트

새 엔드포인트를 출시하기 전에:

  • 리소스 URL이 명명 규칙을 따르는가 (복수형, 케밥-케이스, 동사 없음)
  • 올바른 HTTP 메서드가 사용되었는가 (읽기는 GET, 생성을 POST 등)
  • 적절한 상태 코드가 반환되는가 (모든 것에 200 사용 금지)
  • 입력이 스키마로 유효성 검사되었는가 (Zod, Pydantic, Bean Validation)
  • 오류 응답이 코드와 메시지를 포함한 표준 형식을 따르는가
  • 목록 엔드포인트에 페이지네이션이 구현되었는가 (커서 또는 오프셋)
  • 인증이 필요한가 (또는 명시적으로 공개로 표시되었는가)
  • 권한 부여가 확인되었는가 (사용자는 자신의 리소스에만 접근 가능)
  • 속도 제한이 구성되었는가
  • 응답이 내부 세부 정보를 노출하지 않는가 (스택 추적, SQL 오류)
  • 기존 엔드포인트와 명명 규칙이 일관적인가 (camelCase vs snake_case)
  • 문서화되었는가 (OpenAPI/Swagger 명세 업데이트)

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

관련 스킬