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.
npx -y skills add ComeOnOliver/skillshub --skill api-contract-designAssembled 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 μ 곡
- λ³κ²½ μ΄λ ₯ κ΄λ¦¬
- λ²μ κ΄λ¦¬ μ λ΅