agentsclimarketplace

Api contract design

Skill ComeOnOliver/skillshub/skills/aiskillstore/marketplace/doyajin174/api-contract-design

🧠 The right skill, one API call. AI agent skills registry with token-efficient skill resolution. 5,000+ skills from 500+ top repos.

Install
npx -y skills add ComeOnOliver/skillshub --skill api-contract-design

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

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.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

10.2 KB, as published. Nobody here has run it

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

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.