
Api Design
- 53 installs
- 19 repo stars
- Updated January 20, 2026
- miles990/claude-software-skills
Helps with backend & apis tasks.
About
api-design is a Claude Code skill for backend & apis. It helps solo builders move faster with AI-assisted development.
- api-design
- Backend & APIs
- AI-coding skill
Api Design by the numbers
- 53 all-time installs (skills.sh)
- +2 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #3,215 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/miles990/claude-software-skills --skill api-designAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 53 |
|---|---|
| repo stars | ★ 19 |
| Last updated | January 20, 2026 |
| Repository | miles990/claude-software-skills ↗ |
What it does
Helps with backend & apis tasks.
Files
API Design
Overview
Design principles for building APIs that are intuitive, consistent, and scalable. Covers REST, GraphQL, gRPC, and real-time protocols.
---
RESTful API Design
Resource Naming
✅ Good (nouns, plural):
GET /users # List users
GET /users/123 # Get user
POST /users # Create user
PUT /users/123 # Update user
DELETE /users/123 # Delete user
❌ Bad (verbs, actions):
GET /getUsers
POST /createUser
POST /users/123/deleteNested Resources
# Hierarchical relationship
GET /users/123/orders # User's orders
GET /users/123/orders/456 # Specific order
# Alternative: Query parameter for filtering
GET /orders?userId=123 # Filter orders by user
# Rule: Nest max 2 levels deep
❌ /users/123/orders/456/items/789/reviews
✅ /order-items/789/reviewsHTTP Methods & Status Codes
| Method | Purpose | Success | Error |
|---|---|---|---|
| GET | Read | 200 | 404 |
| POST | Create | 201 | 400, 409 |
| PUT | Replace | 200 | 400, 404 |
| PATCH | Partial update | 200 | 400, 404 |
| DELETE | Remove | 204 | 404 |
// Response structure
interface ApiResponse<T> {
data: T;
meta?: {
page: number;
limit: number;
total: number;
};
}
interface ApiError {
error: {
code: string; // Machine-readable
message: string; // Human-readable
details?: object; // Validation errors, etc.
};
}Pagination
// Offset-based (simple, has issues with large datasets)
GET /users?page=2&limit=20
// Cursor-based (stable, performant)
GET /users?cursor=eyJpZCI6MTIzfQ&limit=20
// Response
{
"data": [...],
"meta": {
"nextCursor": "eyJpZCI6MTQzfQ",
"hasMore": true
}
}Filtering & Sorting
// Query parameters
GET /products?category=electronics&minPrice=100&maxPrice=500
GET /products?sort=-createdAt,name // - prefix for descending
// Filter operators
GET /users?age[gte]=18&age[lte]=65
GET /users?status[in]=active,pending
GET /users?name[like]=john*---
GraphQL Design
Schema Design
type User {
id: ID!
email: String!
name: String!
posts(first: Int, after: String): PostConnection!
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
comments: [Comment!]!
}
# Connections for pagination (Relay spec)
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
}
type PostEdge {
node: Post!
cursor: String!
}
type Query {
user(id: ID!): User
users(first: Int, after: String, filter: UserFilter): UserConnection!
}
type Mutation {
createUser(input: CreateUserInput!): CreateUserPayload!
updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
}
input CreateUserInput {
email: String!
name: String!
password: String!
}
type CreateUserPayload {
user: User
errors: [Error!]
}Resolver Patterns
// N+1 prevention with DataLoader
const userLoader = new DataLoader(async (ids: string[]) => {
const users = await db.users.findMany({ where: { id: { in: ids } } });
return ids.map(id => users.find(u => u.id === id));
});
const resolvers = {
Post: {
author: (post, _, { loaders }) => loaders.user.load(post.authorId),
},
};
// Field-level authorization
const resolvers = {
User: {
email: (user, _, { currentUser }) => {
if (currentUser.id !== user.id && !currentUser.isAdmin) {
return null; // Hide from other users
}
return user.email;
},
},
};Error Handling
// Union types for expected errors
type CreatePostResult = Post | ValidationError | NotAuthorizedError
// Or use errors field in payload
type CreatePostPayload {
post: Post
errors: [CreatePostError!]
}
union CreatePostError = ValidationError | RateLimitError---
gRPC Design
Protocol Buffers
syntax = "proto3";
package users.v1;
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
// Streaming
rpc WatchUsers(WatchUsersRequest) returns (stream UserEvent);
rpc BatchCreateUsers(stream CreateUserRequest) returns (BatchCreateResponse);
}
message User {
string id = 1;
string email = 2;
string name = 3;
google.protobuf.Timestamp created_at = 4;
}
message GetUserRequest {
string id = 1;
}
message GetUserResponse {
User user = 1;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
UserFilter filter = 3;
}
message ListUsersResponse {
repeated User users = 1;
string next_page_token = 2;
}When to Use gRPC
| Use Case | REST | GraphQL | gRPC |
|---|---|---|---|
| Public API | ✅ | ✅ | ❌ |
| Mobile apps | ✅ | ✅ | ⚠️ |
| Microservices | ⚠️ | ❌ | ✅ |
| Real-time | ❌ | ⚠️ | ✅ |
| Browser clients | ✅ | ✅ | ⚠️ |
---
API Versioning
Strategies
# URL versioning (most common)
GET /v1/users
GET /v2/users
# Header versioning
GET /users
Accept: application/vnd.api+json; version=2
# Query parameter
GET /users?version=2Breaking vs Non-Breaking Changes
Non-Breaking (safe):
✅ Add new optional field
✅ Add new endpoint
✅ Add new optional query parameter
✅ Expand enum values (if client ignores unknown)
Breaking (requires new version):
❌ Remove field
❌ Rename field
❌ Change field type
❌ Make optional field required
❌ Change URL structureDeprecation Strategy
// OpenAPI deprecation
/**
* @deprecated Use /v2/users instead. Will be removed on 2025-06-01.
*/
app.get('/v1/users', ...);
// Response header
res.setHeader('Deprecation', 'true');
res.setHeader('Sunset', 'Sat, 01 Jun 2025 00:00:00 GMT');
res.setHeader('Link', '</v2/users>; rel="successor-version"');---
Real-time APIs
WebSocket
// Server
wss.on('connection', (ws) => {
ws.on('message', (data) => {
const message = JSON.parse(data);
switch (message.type) {
case 'subscribe':
subscriptions.add(ws, message.channel);
break;
case 'unsubscribe':
subscriptions.remove(ws, message.channel);
break;
}
});
});
// Client
const ws = new WebSocket('wss://api.example.com/ws');
ws.send(JSON.stringify({
type: 'subscribe',
channel: 'orders:user:123'
}));
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
handleUpdate(data);
};Server-Sent Events (SSE)
// Server
app.get('/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const sendEvent = (data: object) => {
res.write(`data: ${JSON.stringify(data)}\n\n`);
};
// Subscribe to events
eventEmitter.on('update', sendEvent);
req.on('close', () => {
eventEmitter.off('update', sendEvent);
});
});
// Client
const source = new EventSource('/events');
source.onmessage = (event) => {
const data = JSON.parse(event.data);
handleUpdate(data);
};---
API Documentation
OpenAPI (Swagger)
openapi: 3.0.3
info:
title: User API
version: 1.0.0
paths:
/users:
get:
summary: List users
parameters:
- name: limit
in: query
schema:
type: integer
default: 20
responses:
'200':
description: Success
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
required: [id, email, name]
properties:
id:
type: string
format: uuid
email:
type: string
format: email
name:
type: string---
Security Best Practices
| Practice | Implementation |
|---|---|
| Authentication | Bearer tokens, API keys |
| Rate limiting | X-RateLimit-* headers |
| Input validation | Schema validation (Zod, Joi) |
| CORS | Whitelist allowed origins |
| HTTPS | Always in production |
| Request IDs | X-Request-ID for tracing |
// Rate limit headers
res.setHeader('X-RateLimit-Limit', '100');
res.setHeader('X-RateLimit-Remaining', '95');
res.setHeader('X-RateLimit-Reset', '1640000000');---
Related Skills
- [[architecture-patterns]] - API Gateway, microservices
- [[security-practices]] - Authentication, authorization
- [[documentation]] - API documentation tools
---
Sharp Edges(常見陷阱)
這些是 API 設計中最常見且代價最高的錯誤
SE-1: 破壞性變更 (Breaking Changes)
- 嚴重度: critical
- 情境: 修改現有 API 導致客戶端崩潰,沒有適當的版本控制
- 原因: 刪除欄位、改變欄位類型、修改必填性、重新命名
- 症狀:
- 客戶端突然出現錯誤
- 舊版 App 無法使用
- 用戶投訴「昨天還能用,今天就壞了」
- 檢測:
\-.*field|rename.*property|required.*true.*→.*false|type.*string.*→.*number - 解法: 使用 API 版本控制、只做 additive changes、設定 deprecation 期限
SE-2: 過度取得 (Over-fetching)
- 嚴重度: medium
- 情境: API 回傳太多客戶端不需要的資料
- 原因: 「反正都有就回傳」的心態、沒有考慮不同使用場景
- 症狀:
- API 響應很大但客戶端只用其中一小部分
- 行動裝置載入緩慢
- 頻寬浪費
- 檢測:
select\s*\*|findMany\(\)|findAll\(\)(?!.*select) - 解法: 使用 fields selection、GraphQL、按需求設計 endpoint
SE-3: 取得不足 (Under-fetching)
- 嚴重度: medium
- 情境: 需要呼叫多個 API 才能取得完整資料
- 原因: 過度細分 endpoint、沒有考慮常見使用場景
- 症狀:
- 前端需要 5+ 個 API 呼叫才能渲染一個頁面
- 複雜的前端資料整合邏輯
- N+1 API 請求問題
- 檢測:
Promise\.all\(.*fetch.*fetch.*fetch|\.then\(.*fetch - 解法: 設計聚合 endpoint、使用 include/expand 參數、考慮 BFF pattern
SE-4: 不一致的錯誤格式
- 嚴重度: high
- 情境: 不同 endpoint 回傳不同格式的錯誤,客戶端難以統一處理
- 原因: 沒有統一的錯誤處理規範、不同開發者各自實作
- 症狀:
- 有的錯誤用
error,有的用message,有的用errors - HTTP status code 使用不一致
- 客戶端需要寫很多 if-else 處理不同錯誤格式
- 檢測:
res\.json\(\{.*error|res\.json\(\{.*message|res\.status\(500\).*error - 解法: 定義統一的錯誤回應格式、使用 global error handler、建立錯誤碼系統
SE-5: 缺乏 Rate Limiting
- 嚴重度: critical
- 情境: API 沒有請求頻率限制,容易被濫用或攻擊
- 原因: 「先做出來再說」、不了解風險
- 症狀:
- DDoS 攻擊導致服務癱瘓
- 單一用戶耗盡所有資源
- 雲端帳單爆炸
- 檢測:
app\.use\((?!.*rateLimit)|router\.(?!.*limit)|express\(\)(?!.*rate) - 解法: 實作 rate limiting middleware、使用 Redis 追蹤請求、設定合理的限制
---
Validations
V-1: 禁止 HTTP status 200 回傳錯誤
- 類型: regex
- 嚴重度: high
- 模式:
res\.json\(\s*\{\s*error|res\.send\(\s*\{\s*error|\.json\(\{.*success:\s*false - 訊息: Error responses should use appropriate HTTP status codes (4xx/5xx)
- 修復建議: Use
res.status(400).json({ error: ... })for client errors - 適用:
*.ts,*.js
V-2: API 路徑使用動詞
- 類型: regex
- 嚴重度: medium
- 模式:
(get|create|update|delete|fetch|remove|add)\/|\/get|\/create|\/update|\/delete - 訊息: REST API paths should use nouns, not verbs (use HTTP methods instead)
- 修復建議: Change
/getUserstoGET /users,/createUsertoPOST /users - 適用:
*.ts,*.js
V-3: 缺少輸入驗證
- 類型: regex
- 嚴重度: critical
- 模式:
req\.body\.\w+(?!.*validate|.*schema|.*zod|.*joi|.*yup) - 訊息: Request body accessed without apparent validation
- 修復建議: Add validation with Zod/Joi before using request data
- 適用:
*.ts,*.js
V-4: 硬編碼 HTTP status code
- 類型: regex
- 嚴重度: low
- 模式:
res\.status\(\d{3}\) - 訊息: Consider using named status codes for readability
- 修復建議: Use
HttpStatus.OKor constants instead of magic numbers - 適用:
*.ts,*.js
V-5: 缺少 API 版本控制
- 類型: regex
- 嚴重度: medium
- 模式:
app\.(get|post|put|delete)\s*\(\s*['"]\/(?!v\d|api\/v\d) - 訊息: API route missing version prefix
- 修復建議: Add version prefix:
/api/v1/usersor use header-based versioning - 適用:
*.ts,*.js
# OpenAPI 3.1 Specification Template
# Usage: Copy to openapi.yaml or api-spec.yaml
# Validate: https://editor.swagger.io/
openapi: 3.1.0
info:
title: My API
description: |
REST API for My Application.
## Authentication
All endpoints require Bearer token authentication.
## Rate Limiting
- 100 requests per minute per IP
- 1000 requests per hour per user
version: 1.0.0
contact:
name: API Support
email: api@example.com
license:
name: MIT
url: https://opensource.org/licenses/MIT
servers:
- url: https://api.example.com/v1
description: Production
- url: https://staging-api.example.com/v1
description: Staging
- url: http://localhost:3000/v1
description: Development
# ===========================================
# Security Schemes
# ===========================================
security:
- bearerAuth: []
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
# ===========================================
# Reusable Schemas
# ===========================================
schemas:
# Base response wrapper
ApiResponse:
type: object
properties:
success:
type: boolean
data:
type: object
error:
$ref: '#/components/schemas/Error'
Error:
type: object
properties:
code:
type: string
example: VALIDATION_ERROR
message:
type: string
example: Invalid input data
details:
type: array
items:
type: object
# Pagination
Pagination:
type: object
properties:
page:
type: integer
minimum: 1
default: 1
limit:
type: integer
minimum: 1
maximum: 100
default: 20
total:
type: integer
totalPages:
type: integer
# User model
User:
type: object
required:
- id
- email
properties:
id:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
role:
type: string
enum: [user, admin]
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
CreateUserRequest:
type: object
required:
- email
- password
properties:
email:
type: string
format: email
password:
type: string
minLength: 8
name:
type: string
# Item model (example resource)
Item:
type: object
required:
- id
- title
properties:
id:
type: string
format: uuid
title:
type: string
description:
type: string
status:
type: string
enum: [draft, published, archived]
createdAt:
type: string
format: date-time
# ===========================================
# Reusable Parameters
# ===========================================
parameters:
PageParam:
name: page
in: query
schema:
type: integer
minimum: 1
default: 1
LimitParam:
name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 20
IdParam:
name: id
in: path
required: true
schema:
type: string
format: uuid
# ===========================================
# Reusable Responses
# ===========================================
responses:
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Unauthorized:
description: Authentication required
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
ValidationError:
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
# ===========================================
# API Paths
# ===========================================
paths:
# Health check
/health:
get:
summary: Health check
tags: [System]
security: []
responses:
'200':
description: Service is healthy
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: ok
timestamp:
type: string
format: date-time
# Users
/users:
get:
summary: List users
tags: [Users]
parameters:
- $ref: '#/components/parameters/PageParam'
- $ref: '#/components/parameters/LimitParam'
responses:
'200':
description: List of users
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
pagination:
$ref: '#/components/schemas/Pagination'
post:
summary: Create user
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/ValidationError'
/users/{id}:
get:
summary: Get user by ID
tags: [Users]
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: User details
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
$ref: '#/components/responses/NotFound'
tags:
- name: System
description: System endpoints
- name: Users
description: User management
API Design Templates
REST (OpenAPI) and GraphQL schema templates.
Files
| Template | Purpose |
|---|---|
openapi.yaml | OpenAPI 3.1 REST API specification |
schema.graphql | GraphQL SDL schema |
Usage
OpenAPI (REST)
cp templates/openapi.yaml ./api-spec.yaml
# Validate
npx @redocly/cli lint api-spec.yaml
# Generate docs
npx @redocly/cli build-docs api-spec.yaml
# Generate client
npx openapi-typescript api-spec.yaml -o src/types/api.tsGraphQL
cp templates/schema.graphql ./src/schema.graphql
# With Apollo Server
npm install @apollo/server graphql
# Generate types (graphql-codegen)
npx graphql-codegenOpenAPI Features
Included Patterns
- JWT Bearer authentication
- Pagination (page/limit)
- Reusable schemas and responses
- Error handling structure
- Multiple server environments
Customization
Add new endpoints:
paths:
/products:
get:
summary: List products
tags: [Products]
responses:
'200':
description: SuccessGraphQL Features
Included Patterns
- Connection-based pagination
- Input types for mutations
- Custom directives (@auth, @rateLimit)
- Subscriptions support
- Enum types
Customization
Add new types:
type Product {
id: UUID!
name: String!
price: Float!
}
extend type Query {
products(pagination: PaginationInput): ProductConnection!
}Tools Comparison
| Aspect | REST (OpenAPI) | GraphQL |
|---|---|---|
| Best for | CRUD, public APIs | Complex queries, real-time |
| Caching | HTTP caching | Custom/Apollo |
| Versioning | URL or header | Schema evolution |
| Documentation | Swagger UI, Redoc | GraphiQL, Apollo Studio |
Code Generation
TypeScript (REST)
npm install openapi-typescript
npx openapi-typescript api-spec.yaml -o types/api.tsTypeScript (GraphQL)
npm install @graphql-codegen/cli
npx graphql-codegen init# GraphQL Schema Template
# Usage: Copy to schema.graphql or src/schema.graphql
# Tools: Apollo Server, GraphQL Yoga, Pothos
# ===========================================
# Scalar Types
# ===========================================
scalar DateTime
scalar UUID
scalar JSON
# ===========================================
# Enums
# ===========================================
enum Role {
USER
ADMIN
}
enum ItemStatus {
DRAFT
PUBLISHED
ARCHIVED
}
enum SortOrder {
ASC
DESC
}
# ===========================================
# Input Types
# ===========================================
input PaginationInput {
page: Int = 1
limit: Int = 20
}
input CreateUserInput {
email: String!
password: String!
name: String
}
input UpdateUserInput {
email: String
name: String
role: Role
}
input CreateItemInput {
title: String!
description: String
status: ItemStatus = DRAFT
}
input UpdateItemInput {
title: String
description: String
status: ItemStatus
}
input ItemFilterInput {
status: ItemStatus
authorId: UUID
search: String
}
input ItemSortInput {
field: ItemSortField!
order: SortOrder = DESC
}
enum ItemSortField {
CREATED_AT
UPDATED_AT
TITLE
}
# ===========================================
# Types
# ===========================================
type User {
id: UUID!
email: String!
name: String
role: Role!
items: [Item!]!
createdAt: DateTime!
updatedAt: DateTime!
}
type Item {
id: UUID!
title: String!
description: String
status: ItemStatus!
author: User!
createdAt: DateTime!
updatedAt: DateTime!
}
# ===========================================
# Pagination
# ===========================================
type PageInfo {
page: Int!
limit: Int!
total: Int!
totalPages: Int!
hasNextPage: Boolean!
hasPreviousPage: Boolean!
}
type UserConnection {
nodes: [User!]!
pageInfo: PageInfo!
}
type ItemConnection {
nodes: [Item!]!
pageInfo: PageInfo!
}
# ===========================================
# Query
# ===========================================
type Query {
# Users
me: User
user(id: UUID!): User
users(pagination: PaginationInput): UserConnection!
# Items
item(id: UUID!): Item
items(
pagination: PaginationInput
filter: ItemFilterInput
sort: ItemSortInput
): ItemConnection!
# Health check
health: HealthStatus!
}
type HealthStatus {
status: String!
timestamp: DateTime!
version: String!
}
# ===========================================
# Mutation
# ===========================================
type Mutation {
# Auth
login(email: String!, password: String!): AuthPayload!
register(input: CreateUserInput!): AuthPayload!
logout: Boolean!
# Users
updateUser(id: UUID!, input: UpdateUserInput!): User!
deleteUser(id: UUID!): Boolean!
# Items
createItem(input: CreateItemInput!): Item!
updateItem(id: UUID!, input: UpdateItemInput!): Item!
deleteItem(id: UUID!): Boolean!
publishItem(id: UUID!): Item!
archiveItem(id: UUID!): Item!
}
type AuthPayload {
token: String!
user: User!
}
# ===========================================
# Subscription (Optional)
# ===========================================
type Subscription {
itemCreated: Item!
itemUpdated(id: UUID): Item!
}
# ===========================================
# Directives (Custom)
# ===========================================
# Authentication directive
directive @auth(
requires: Role = USER
) on FIELD_DEFINITION
# Rate limiting directive
directive @rateLimit(
max: Int!
window: Int! # seconds
) on FIELD_DEFINITION
# Caching directive
directive @cacheControl(
maxAge: Int
scope: CacheControlScope
) on FIELD_DEFINITION | OBJECT
enum CacheControlScope {
PUBLIC
PRIVATE
}
Related skills
Backend & APIsbackend