ACTIVATION: APIs are contracts. Design them to survive 10x scale and 5 years of evolution.
🎯 Core Principles
Contract First — Define the interface before implementation
Version Explicitly — Never break existing clients
Type Safety — Runtime validation with Zod/io-ts
Documentation Auto-Generated — OpenAPI from code, not hand-written
Idempotency — Safe retries for all mutations
🏗️ API Paradigm Selection
Paradigm
Use When
Trade-off
관련 스킬
REST
Public APIs, caching, CDN-friendly
Multiple round-trips
GraphQL
Complex data graphs, mobile apps
Caching complexity
tRPC
TypeScript monorepo, internal APIs
TypeScript-only
gRPC
Microservices, high throughput
HTTP/2 required
WebSocket
Real-time, bidirectional
Connection overhead
Decision Matrix
function selectAPIParadigm(context: APIContext): APIType {
if (context.clientDiversity === 'high') return 'REST' // Universal support
if (context.dataComplexity === 'high') return 'GraphQL' // Flexible queries
if (context.typeScriptOnly) return 'tRPC' // End-to-end types
if (context.realTimeRequired) return 'WebSocket' // Live updates
if (context.microservices) return 'gRPC' // Binary protocol
return 'REST' // Default
}
📐 REST API Patterns
Resource Naming
// ❌ BAD — Verbs in URL, inconsistent pluralization
GET /getUserData/123
POST /createOrder
DELETE /removeProduct/456
// ✅ GOOD — Nouns, plural collections, HTTP verbs for actions
GET /users/123
POST /orders
DELETE /products/456
// ✅ Complex relationships
GET /users/123/orders // User's orders
GET /orders/456/items // Order items
GET /products/789/reviews // Product reviews
HTTP Status Codes (Use Correctly)
Code
Use For
Don't Use For
200
Success
Everything
201
Created
Generic success
204
No content (delete)
Empty error
400
Bad request (client error)
Server errors
401
Unauthorized (need auth)
Forbidden
403
Forbidden (no permission)
Not found
404
Not found
Validation errors
409
Conflict (duplicate, state)
Generic error
422
Validation failed
400
429
Rate limited
503
500
Server error
Client errors
503
Service unavailable
Rate limiting
Error Response Format
// ✅ Standard error format
interface APIError {
error: {
code: string; // Machine-readable: "INVALID_EMAIL"
message: string; // Human-readable: "Email format is invalid"
details?: Array<{
field: string;
message: string;
}>;
requestId: string; // For tracing
}
}
// Example
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Request validation failed",
"details": [
{ "field": "email", "message": "Invalid email format" },
{ "field": "age", "message": "Must be at least 18" }
],
"requestId": "req_abc123"
}
}
Pagination Patterns
// ✅ Cursor-based (preferred for infinite scroll)
GET /users?cursor=eyJpZCI6MTAwfQ&limit=20
interface CursorPaginatedResponse<T> {
data: T[];
nextCursor: string | null; // Null when no more data
hasMore: boolean;
}
// ✅ Offset-based (for jump-to-page UIs)
GET /users?page=3&perPage=20
interface OffsetPaginatedResponse<T> {
data: T[];
meta: {
currentPage: number;
totalPages: number;
totalCount: number;
perPage: number;
}
}
🕸️ GraphQL Patterns
Schema Design
# ✅ Use nullable for fields that might not exist
type User {
id: ID!
email: String!
name: String # Can be null if user hasn't set name
avatar: String # Can be null
createdAt: DateTime!
}
# ✅ Input types for mutations
input CreateUserInput {
email: String!
name: String
}
type Mutation {
createUser(input: CreateUserInput!): User!
}