Claude Code Plugins

Community-maintained marketplace

Feedback

api-contract-design

@Doyajin174/myskills
0
0

Design APIs using schema-first approach with OpenAPI/Swagger. Use when creating new APIs, documenting existing ones, or when frontend/backend teams need to work in parallel. Covers OpenAPI spec, validation, and code generation.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name api-contract-design
description Design APIs using schema-first approach with OpenAPI/Swagger. Use when creating new APIs, documenting existing ones, or when frontend/backend teams need to work in parallel. Covers OpenAPI spec, validation, and code generation.
allowed-tools Read, Glob, Grep, Edit, Write, Bash
license MIT
metadata [object Object]

API Contract Design

OpenAPI(Swagger) 기반 스키마 우선 API 설계 스킬입니다.

Core Principle

"코드보다 계약(Contract)이 먼저다." "프론트엔드와 백엔드가 동시에 개발할 수 있게 API를 먼저 정의한다."

Schema-First vs Code-First

접근법 장점 단점
Schema-First (권장) 병렬 개발 가능, 명확한 계약 초기 설계 시간 필요
Code-First 빠른 시작 문서와 코드 불일치 위험

OpenAPI 기본 구조

openapi.yaml

openapi: 3.1.0
info:
  title: My API
  version: 1.0.0
  description: API for My Application

servers:
  - url: https://api.example.com/v1
    description: Production
  - url: http://localhost:3000/api
    description: Development

paths:
  /users:
    get:
      summary: Get all users
      operationId: getUsers
      tags:
        - Users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      summary: Create a new user
      operationId: createUser
      tags:
        - Users
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserRequest'
      responses:
        '201':
          description: User created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          description: Email already exists
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /users/{userId}:
    get:
      summary: Get user by ID
      operationId: getUserById
      tags:
        - Users
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          $ref: '#/components/responses/NotFound'

components:
  schemas:
    User:
      type: object
      required:
        - id
        - email
        - name
        - createdAt
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        name:
          type: string
          minLength: 1
          maxLength: 100
        avatarUrl:
          type: string
          format: uri
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    CreateUserRequest:
      type: object
      required:
        - email
        - name
        - password
      properties:
        email:
          type: string
          format: email
        name:
          type: string
          minLength: 1
          maxLength: 100
        password:
          type: string
          minLength: 8

    UserListResponse:
      type: object
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/User'
        pagination:
          $ref: '#/components/schemas/Pagination'

    Pagination:
      type: object
      required:
        - page
        - limit
        - total
        - totalPages
      properties:
        page:
          type: integer
        limit:
          type: integer
        total:
          type: integer
        totalPages:
          type: integer

    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
        message:
          type: string
        details:
          type: object

  responses:
    BadRequest:
      description: Bad request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

security:
  - BearerAuth: []

폴더 구조

api/
├── openapi.yaml          # 메인 스펙
├── paths/                # 엔드포인트별 분리
│   ├── users.yaml
│   ├── posts.yaml
│   └── auth.yaml
├── schemas/              # 스키마 분리
│   ├── user.yaml
│   ├── post.yaml
│   └── common.yaml
└── generated/            # 자동 생성 코드
    ├── types.ts
    └── client.ts

분리된 스펙 (paths/users.yaml)

# api/paths/users.yaml
/users:
  get:
    $ref: '../operations/users/getUsers.yaml'
  post:
    $ref: '../operations/users/createUser.yaml'

메인 스펙에서 참조

# api/openapi.yaml
paths:
  /users:
    $ref: './paths/users.yaml#/~1users'

TypeScript 타입 생성

openapi-typescript

npm install -D openapi-typescript
# 타입 생성
npx openapi-typescript ./api/openapi.yaml -o ./src/types/api.ts

생성된 타입 사용

import type { paths, components } from './types/api';

type User = components['schemas']['User'];
type CreateUserRequest = components['schemas']['CreateUserRequest'];

// API 응답 타입
type GetUsersResponse = paths['/users']['get']['responses']['200']['content']['application/json'];

API 클라이언트 생성

openapi-fetch (권장)

npm install openapi-fetch
// lib/api-client.ts
import createClient from 'openapi-fetch';
import type { paths } from './types/api';

export const api = createClient<paths>({
  baseUrl: process.env.NEXT_PUBLIC_API_URL,
});

// 사용
const { data, error } = await api.GET('/users', {
  params: {
    query: { page: 1, limit: 20 },
  },
});

const { data: user } = await api.POST('/users', {
  body: {
    email: 'user@example.com',
    name: 'John',
    password: 'password123',
  },
});

Orval (코드 생성)

npm install -D orval
// orval.config.ts
export default {
  api: {
    input: './api/openapi.yaml',
    output: {
      mode: 'tags-split',
      target: './src/api',
      schemas: './src/api/schemas',
      client: 'react-query',
    },
  },
};

요청 검증

Zod + OpenAPI

// 스키마에서 Zod 스키마 생성
import { z } from 'zod';

// OpenAPI 스펙 기반 Zod 스키마
export const CreateUserRequestSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
  password: z.string().min(8),
});

// API 라우트에서 검증
export async function POST(request: Request) {
  const body = await request.json();

  const result = CreateUserRequestSchema.safeParse(body);
  if (!result.success) {
    return Response.json(
      { code: 'VALIDATION_ERROR', message: result.error.message },
      { status: 400 }
    );
  }

  // result.data는 타입 안전
  const user = await createUser(result.data);
  return Response.json(user, { status: 201 });
}

API 문서 UI

Swagger UI

npm install swagger-ui-react
// app/api-docs/page.tsx
'use client';

import SwaggerUI from 'swagger-ui-react';
import 'swagger-ui-react/swagger-ui.css';

export default function ApiDocs() {
  return <SwaggerUI url="/api/openapi.yaml" />;
}

Scalar (모던 대안)

npm install @scalar/nextjs-api-reference
// app/api-docs/page.tsx
import { ApiReference } from '@scalar/nextjs-api-reference';

export default function ApiDocs() {
  return (
    <ApiReference
      configuration={{
        spec: {
          url: '/api/openapi.yaml',
        },
      }}
    />
  );
}

버전 관리

URL 버전 관리

servers:
  - url: https://api.example.com/v1
  - url: https://api.example.com/v2

헤더 버전 관리

parameters:
  - name: API-Version
    in: header
    schema:
      type: string
      enum: ['2024-01-01', '2024-06-01']

Workflow

Schema-First 개발 흐름

1. API 스펙 작성 (openapi.yaml)
   ↓
2. 팀 리뷰 (PR)
   ↓
3. 타입 생성 (openapi-typescript)
   ↓
4. 병렬 개발
   - Frontend: Mock 서버로 개발
   - Backend: 스펙 기반 구현
   ↓
5. 통합 테스트

Mock 서버

# Prism (Stoplight)
npm install -D @stoplight/prism-cli

# Mock 서버 실행
npx prism mock ./api/openapi.yaml

Checklist

스펙 작성

  • 모든 엔드포인트 정의
  • Request/Response 스키마 정의
  • 에러 응답 정의
  • 인증 방식 정의
  • 예제 데이터 포함

타입 안전성

  • TypeScript 타입 생성
  • 요청 검증 (Zod)
  • 응답 타입 체크

문서화

  • API 문서 UI 제공
  • 변경 이력 관리
  • 버전 관리 전략

References