Skip to content
api-contract-design logo

api-contract-design

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.

aiskillstore/marketplace0installs429stars

SKILL.md

Full skill instructions

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: '[email protected]',
    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