Agent Skills: 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.

UncategorizedID: aiskillstore/marketplace/api-contract-design

Install this agent skill to your local

pnpm dlx add-skill https://github.com/aiskillstore/marketplace/tree/HEAD/skills/doyajin174/api-contract-design

Skill Files

Browse the full folder contents for api-contract-design.

Download Skill

Loading file tree…

skills/doyajin174/api-contract-design/SKILL.md

Skill Metadata

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.

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