Communitygithub.com

API 설계 패턴

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

API 설계 패턴란 무엇인가요?

이 스킬은 일관되고 개발자 친화적인 REST API를 설계하기 위한 모범 사례와 규칙을 제공합니다. 새로운 API 엔드포인트를 설계하거나 기존 API 계약을 검토할 때 활성화되며, URL 구조, HTTP 메서드 및 상태 코드, 응답 형식(성공, 컬렉션, 오류), 페이지네이션(오프셋 기반 및 커서 기반), 필터링, 정렬, 검색, 인증 및 인가, 속도 제한, 버전 관리 전략 등 주요 측면을 다룹니다. 또한 TypeScript(Next.js), Python(Django REST Framework), Go(net/http)를 사용한 구현 패턴을 포함하여 실제 코드 예제를 제공하며, 엔드포인트 출시 전 최종 점검을 위한 체크리스트도 포함합니다. 이 스킬은 모든 REST API 프레임워크와 플랫폼(Claude Code, Cursor, Codex 등)에서 사용할 수 있습니다.

지원 대상~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/affaan-m/ECC/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

명명 규칙

# GOOD (옳은 예)
/api/v1/team-members          # 여러 단어 리소스는 케밥 표기법
/api/v1/orders?status=active  # 필터링은 쿼리 파라미터 사용
/api/v1/users/123/orders      # 소유 관계는 중첩 리소스 사용

# BAD (잘못된 예)
/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 헤더 포함

일반적인 실수

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

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

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

# BAD: 생성된 리소스에 200 사용
# GOOD: 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"
  }
}

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

언제 무엇을 사용할까

사용 사례페이지네이션 유형
관리자 대시보드, 작은 데이터셋 (<1만)오프셋
무한 스크롤, 피드, 대규모 데이터셋커서
공개 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

희소 필드셋

# 지정된 필드만 반환 (페이로드 감소)
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/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

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.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

관련 스킬