Giao diện
🔌 API DESIGN
REST, GraphQL, gRPC - Chọn đúng Paradigm, Thiết kế đúng Pattern
🎯 Mục tiêu Học tập
Sau khi hoàn thành module này, bạn sẽ:
- So sánh và chọn đúng giữa REST, GraphQL, gRPC
- Thiết kế API Versioning strategy phù hợp
- Implement Pagination hiệu quả (cursor vs offset)
- Áp dụng Idempotency cho retry-safe operations
- Xây dựng Error Model chuẩn RFC 7807
- Triển khai Rate Limiting và Auth patterns
- Đảm bảo Backward Compatibility khi evolve schema
- Tích hợp Observability Hooks cho production
📋 Prerequisites
📚 Kiến thức cần có
- 📡 Sync Communication - HTTP basics, request/response
- 📬 Async Messaging - Event-driven patterns
- 📊 Consistency Models - ACID vs BASE
1. API Paradigms: REST vs GraphQL vs gRPC
1.1 REST: The Universal Standard
NOTE
🎓 Giáo sư Tom: REST (Representational State Transfer) được Roy Fielding định nghĩa năm 2000. Đây không phải là protocol mà là architectural style với 6 constraints. Hầu hết "REST APIs" ngày nay thực ra là "HTTP APIs" - không hoàn toàn tuân thủ REST constraints.
┌─────────────────────────────────────────────────────────────────────────┐
│ REST ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ CLIENT SERVER │
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
│ │ │ HTTP Request │ │ │
│ │ GET /users/123 │────────────────►│ Resource: /users/{id} │ │
│ │ Accept: json │ │ │ │
│ │ │◄────────────────│ { "id": 123, "name": ... } │ │
│ │ │ HTTP Response │ │ │
│ └─────────────────┘ └─────────────────────────────┘ │
│ │
│ REST Constraints: │
│ ✅ Stateless - Mỗi request chứa đủ thông tin │
│ ✅ Uniform Interface - Chuẩn hóa resource representation │
│ ✅ Cacheable - Response có thể cache │
│ ✅ Client-Server - Tách biệt concerns │
│ ✅ Layered System - Có thể thêm proxy, gateway │
│ ⚠️ Code on Demand (optional) - Server gửi executable code │
│ │
└─────────────────────────────────────────────────────────────────────────┘HTTP Methods Cheat Sheet:
| Method | Idempotent | Safe | Use Case |
|---|---|---|---|
GET | ✅ Yes | ✅ Yes | Đọc resource |
POST | ❌ No | ❌ No | Tạo resource mới |
PUT | ✅ Yes | ❌ No | Replace toàn bộ resource |
PATCH | ❌ No* | ❌ No | Partial update |
DELETE | ✅ Yes | ❌ No | Xóa resource |
*PATCH có thể idempotent nếu thiết kế đúng cách
1.2 GraphQL: The Flexible Query Language
TIP
🔧 Kỹ sư Raizo: GraphQL giải quyết 2 vấn đề lớn của REST: Over-fetching (lấy nhiều hơn cần) và Under-fetching (phải gọi nhiều endpoints). Nhưng đổi lại, bạn phải handle N+1 query problem và complexity budgeting.
graphql
# GraphQL Query - Client chọn chính xác fields cần
query GetUserWithOrders {
user(id: "123") {
id
name
email
orders(first: 5) {
id
total
items {
productName
quantity
}
}
}
}
# Response - Chỉ những gì client yêu cầu
{
"data": {
"user": {
"id": "123",
"name": "John Doe",
"email": "john@example.com",
"orders": [
{
"id": "ord-001",
"total": 150.00,
"items": [...]
}
]
}
}
}GraphQL Trade-offs:
| Ưu điểm | Nhược điểm |
|---|---|
| Flexible queries | N+1 query problem |
| Single endpoint | Complexity budgeting |
| Strong typing | Caching khó hơn REST |
| Self-documenting | Learning curve |
1.3 gRPC: The Performance Champion
┌─────────────────────────────────────────────────────────────────────────┐
│ gRPC ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ CLIENT SERVER │
│ ┌─────────────────┐ ┌─────────────────────┐ │
│ │ Generated Stub │ HTTP/2 + Protobuf │ Service Implementation│ │
│ │ │◄─────────────────────►│ │ │
│ │ userClient. │ Binary, Fast │ func GetUser(...) { │ │
│ │ GetUser(req) │ Multiplexed │ return &User{...} │ │
│ └─────────────────┘ │ } │ │
│ └─────────────────────┘ │
│ │
│ Streaming Modes: │
│ ├── Unary: Request → Response (như REST) │
│ ├── Server Streaming: Request → Stream of Responses │
│ ├── Client Streaming: Stream of Requests → Response │
│ └── Bidirectional: Stream ↔ Stream (real-time) │
│ │
└─────────────────────────────────────────────────────────────────────────┘protobuf
// user.proto - Protocol Buffer definition
syntax = "proto3";
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (stream User); // Server streaming
}
message GetUserRequest {
string user_id = 1;
}
message User {
string id = 1;
string name = 2;
string email = 3;
int64 created_at = 4;
}1.4 Decision Table: Khi nào dùng gì?
| Criteria | REST | GraphQL | gRPC |
|---|---|---|---|
| Best for | Public APIs, CRUD | Mobile apps, complex queries | Microservices, real-time |
| Performance | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| Flexibility | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| Caching | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐ |
| Browser Support | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ (cần proxy) |
| Learning Curve | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
2. API Versioning Strategies
IMPORTANT
Versioning là contract với consumers. Một khi publish API version, bạn không được breaking change mà không warning.
2.1 Versioning Approaches
┌─────────────────────────────────────────────────────────────────────────┐
│ API VERSIONING STRATEGIES │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1️⃣ URL PATH VERSIONING (Recommended for most cases) │
│ ────────────────────────────────────────────────────────────────────── │
│ GET /v1/users/123 │
│ GET /v2/users/123 │
│ │
│ ✅ Explicit, visible │
│ ✅ Easy to route (nginx, API Gateway) │
│ ❌ Violates REST "resource should have one URI" │
│ │
│ 2️⃣ HEADER VERSIONING │
│ ────────────────────────────────────────────────────────────────────── │
│ GET /users/123 │
│ Accept: application/vnd.company.v2+json │
│ │
│ ✅ Clean URLs │
│ ✅ Pure REST │
│ ❌ Hidden, harder to test (curl, browser) │
│ │
│ 3️⃣ QUERY PARAMETER VERSIONING │
│ ────────────────────────────────────────────────────────────────────── │
│ GET /users/123?version=2 │
│ │
│ ✅ Easy to test │
│ ❌ Optional params can be forgotten │
│ ❌ Caching issues (same URL, different responses) │
│ │
└─────────────────────────────────────────────────────────────────────────┘2.2 Semantic Versioning for APIs
MAJOR.MINOR.PATCH
│ │ └── Bug fixes (backward compatible)
│ └──────── New features (backward compatible)
└────────────── Breaking changesBreaking Changes (cần bump MAJOR):
| Change Type | Breaking? | Example |
|---|---|---|
| Remove field | ✅ Yes | user.age removed |
| Rename field | ✅ Yes | user.name → user.full_name |
| Change field type | ✅ Yes | id: number → id: string |
| Add required field (request) | ✅ Yes | New required param |
| Add optional field | ❌ No | New optional param |
| Add new endpoint | ❌ No | GET /v1/analytics |
TIP
🔧 Kỹ sư Raizo: Rule of thumb: Bump MAJOR version khi bất kỳ existing client nào sẽ break nếu không update code. Với internal APIs, bạn có thể aggressive hơn. Với public APIs, support ít nhất 2 versions song song và announce deprecation 6-12 tháng trước.
3. Pagination Patterns
3.1 Offset-based Pagination
typescript
// Request
GET /users?limit=20&offset=40
// Response
{
"data": [...],
"pagination": {
"limit": 20,
"offset": 40,
"total": 1500
}
}┌─────────────────────────────────────────────────────────────────────────┐
│ OFFSET PAGINATION PROBLEM │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Page 1 (offset=0): [A] [B] [C] [D] [E] │
│ │
│ ───── User đang xem Page 1, meanwhile có insert mới ───── │
│ │
│ Data sau insert [X]: [X] [A] [B] [C] [D] [E] │
│ │
│ Page 2 (offset=5): [E] [F] [G] [H] [I] ← Item E bị duplicate! │
│ │
│ ⚠️ Vấn đề: │
│ • Insert/Delete giữa requests → missing hoặc duplicate items │
│ • OFFSET lớn → performance chậm (DB scan từ đầu) │
│ │
└─────────────────────────────────────────────────────────────────────────┘3.2 Cursor-based Pagination (Keyset)
typescript
// Request - dùng cursor thay vì offset
GET /users?limit=20&cursor=user_123
// Response
{
"data": [...],
"pagination": {
"next_cursor": "user_143",
"has_more": true
}
}sql
-- Cursor-based query (efficient)
SELECT * FROM users
WHERE id > 'user_123' -- Cursor là last seen ID
ORDER BY id ASC
LIMIT 20;
-- So với Offset (inefficient với offset lớn)
SELECT * FROM users
ORDER BY id ASC
LIMIT 20 OFFSET 10000; -- Phải scan 10000 rows đầu tiên3.3 Pagination Decision Table
| Factor | Offset | Cursor |
|---|---|---|
| Jump to page N | ✅ Easy | ❌ Not possible |
| Consistent results | ❌ Drift possible | ✅ Stable |
| Performance (large dataset) | ❌ O(offset) | ✅ O(1) |
| Implementation | ⭐⭐⭐⭐⭐ Simple | ⭐⭐⭐ Complex |
| Infinite scroll | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| Total count | ✅ Easy | ❌ Expensive |
WARNING
Khi nào dùng Cursor?
- Dataset > 10,000 items
- Real-time data (feeds, messages)
- Infinite scroll UI
- Performance-critical applications
4. Idempotency Design
CAUTION
🎓 Giáo sư Tom: Idempotency là property mà một operation có thể thực hiện nhiều lần nhưng kết quả giống như thực hiện 1 lần. Trong distributed systems với network failures, idempotency là BẮT BUỘC cho reliability.
4.1 Idempotency Key Pattern
┌─────────────────────────────────────────────────────────────────────────┐
│ IDEMPOTENCY KEY FLOW │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Client Server │
│ │ │ │
│ │ POST /payments │ │
│ │ Idempotency-Key: uuid-abc-123 │ │
│ │ { amount: 100 } │ │
│ │────────────────────────────────►│ │
│ │ │ │
│ │ ┌────────┴────────┐ │
│ │ │ Check key exists │ │
│ │ │ in idempotency │ │
│ │ │ store? │ │
│ │ └────────┬────────┘ │
│ │ │ │
│ │ ┌──────────────────┴──────────────────┐ │
│ │ │ │ │
│ │ [Key NOT found] [Key FOUND] │
│ │ │ │ │
│ │ ▼ ▼ │
│ │ Process payment Return cached response │
│ │ Store result with key (no re-processing) │
│ │ │ │ │
│ │ └──────────────────┬──────────────────┘ │
│ │ │ │
│ │◄────────────────────────────────│ │
│ │ 201 Created / 200 OK │ │
│ │ (same response both times) │ │
│ │
└─────────────────────────────────────────────────────────────────────────┘4.2 Implementation Example
typescript
// Server-side idempotency handler
async function processPayment(req: Request): Promise<Response> {
const idempotencyKey = req.headers['Idempotency-Key'];
if (!idempotencyKey) {
return { status: 400, error: 'Idempotency-Key header required' };
}
// 1. Check if we've seen this key before
const cached = await idempotencyStore.get(idempotencyKey);
if (cached) {
// Return cached response (no re-processing)
return cached.response;
}
// 2. Process the payment
const result = await paymentService.charge(req.body);
// 3. Store result with idempotency key (TTL: 24h)
await idempotencyStore.set(idempotencyKey, {
response: result,
processedAt: Date.now()
}, { ttl: 86400 });
return result;
}4.3 Idempotency by HTTP Method
| Method | Naturally Idempotent? | Strategy |
|---|---|---|
GET | ✅ Yes | No action needed |
PUT | ✅ Yes | Replace entire resource |
DELETE | ✅ Yes | Delete if exists, no-op if not |
POST | ❌ No | Use Idempotency-Key header |
PATCH | ❌ No | Use conditional update (ETag) |
5. Error Model (RFC 7807)
5.1 Problem Details Standard
┌─────────────────────────────────────────────────────────────────────────┐
│ RFC 7807 - PROBLEM DETAILS │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Standard Fields: │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ { │ │
│ │ "type": "https://api.example.com/errors/insufficient-funds", │ │
│ │ "title": "Insufficient Funds", │ │
│ │ "status": 400, │ │
│ │ "detail": "Account balance $50 is less than transfer $100", │ │
│ │ "instance": "/transfers/abc-123", │ │
│ │ │ │
│ │ // Extension fields (custom) │ │
│ │ "balance": 50.00, │ │
│ │ "required": 100.00, │ │
│ │ "traceId": "trace-xyz-789" │ │
│ │ } │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
│ Content-Type: application/problem+json │
│ │
└─────────────────────────────────────────────────────────────────────────┘5.2 Error Taxonomy
typescript
// Error codes taxonomy
const ErrorCodes = {
// 4xx Client Errors
VALIDATION: {
INVALID_INPUT: 'ERR_VALIDATION_001',
MISSING_FIELD: 'ERR_VALIDATION_002',
INVALID_FORMAT: 'ERR_VALIDATION_003',
},
AUTH: {
UNAUTHORIZED: 'ERR_AUTH_001',
TOKEN_EXPIRED: 'ERR_AUTH_002',
INSUFFICIENT_PERMISSIONS: 'ERR_AUTH_003',
},
RESOURCE: {
NOT_FOUND: 'ERR_RESOURCE_001',
ALREADY_EXISTS: 'ERR_RESOURCE_002',
CONFLICT: 'ERR_RESOURCE_003',
},
RATE_LIMIT: {
TOO_MANY_REQUESTS: 'ERR_RATE_001',
QUOTA_EXCEEDED: 'ERR_RATE_002',
},
// 5xx Server Errors
INTERNAL: {
UNEXPECTED: 'ERR_INTERNAL_001',
SERVICE_UNAVAILABLE: 'ERR_INTERNAL_002',
DEPENDENCY_FAILED: 'ERR_INTERNAL_003',
}
};5.3 Error Response Examples
json
// 400 Bad Request - Validation Error
{
"type": "https://api.example.com/errors/validation",
"title": "Validation Failed",
"status": 400,
"detail": "Request body contains invalid fields",
"errors": [
{
"field": "email",
"code": "ERR_VALIDATION_003",
"message": "Invalid email format"
},
{
"field": "age",
"code": "ERR_VALIDATION_001",
"message": "Age must be a positive integer"
}
],
"traceId": "trace-abc-123"
}
// 429 Too Many Requests
{
"type": "https://api.example.com/errors/rate-limit",
"title": "Rate Limit Exceeded",
"status": 429,
"detail": "You have exceeded the rate limit of 100 requests per minute",
"retryAfter": 45,
"limit": 100,
"remaining": 0,
"resetAt": "2024-01-15T10:30:00Z"
}6. Authentication & Authorization
6.1 Auth Methods Comparison
┌─────────────────────────────────────────────────────────────────────────┐
│ AUTHENTICATION METHODS │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1️⃣ API KEYS │
│ ────────────────────────────────────────────────────────────────────── │
│ Authorization: ApiKey sk_live_abc123 │
│ │
│ ✅ Simple implementation │
│ ✅ Good for server-to-server │
│ ❌ No user context │
│ ❌ Rotation phức tạp │
│ │
│ 2️⃣ JWT (JSON Web Token) │
│ ────────────────────────────────────────────────────────────────────── │
│ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVC... │
│ │
│ ✅ Stateless (no DB lookup) │
│ ✅ Contains user claims │
│ ❌ Cannot revoke (until expiry) │
│ ❌ Token size lớn │
│ │
│ 3️⃣ OAuth 2.0 │
│ ────────────────────────────────────────────────────────────────────── │
│ Authorization: Bearer <access_token> │
│ │
│ ✅ Delegated authorization │
│ ✅ Scoped access │
│ ✅ Token refresh │
│ ❌ Complex implementation │
│ │
│ 4️⃣ mTLS (Mutual TLS) │
│ ────────────────────────────────────────────────────────────────────── │
│ Client presents X.509 certificate │
│ │
│ ✅ Strongest security │
│ ✅ Zero-trust architecture │
│ ❌ Certificate management overhead │
│ ❌ Not suitable for public APIs │
│ │
└─────────────────────────────────────────────────────────────────────────┘6.2 Auth Decision Matrix
| Use Case | Recommended | Why |
|---|---|---|
| Public API (3rd party) | OAuth 2.0 + API Keys | Scoped access, revocable |
| Mobile/SPA | OAuth 2.0 + PKCE | Secure without client secret |
| Microservices (internal) | mTLS / JWT | Zero-trust, service identity |
| Server-to-server | API Keys / mTLS | Simple, secure |
| Real-time (WebSocket) | JWT | Stateless validation |
7. Rate Limiting Patterns
7.1 Token Bucket Algorithm
┌─────────────────────────────────────────────────────────────────────────┐
│ TOKEN BUCKET ALGORITHM │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ TOKEN BUCKET │ │
│ │ ┌───┬───┬───┬───┬───┬───┬───┬───┐ │ │
│ │ │ ● │ ● │ ● │ ● │ ● │ │ │ │ │ Capacity: 10 tokens │
│ │ └───┴───┴───┴───┴───┴───┴───┴───┘ │ Current: 5 tokens │
│ │ ▲ │ │
│ │ │ Refill rate: 2 tokens/second │ │
│ └────────┼────────────────────────────────┘ │
│ │ │
│ Request ─┴─► Take 1 token │
│ │
│ if (bucket.tokens >= 1) { │
│ bucket.tokens -= 1; │
│ processRequest(); // ✅ Allowed │
│ } else { │
│ return 429; // ❌ Rate limited │
│ } │
│ │
│ Burst handling: Bucket can accumulate tokens up to capacity │
│ │
└─────────────────────────────────────────────────────────────────────────┘7.2 Rate Limit Headers
http
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1640995200
Retry-After: 607.3 Distributed Rate Limiting
typescript
// Redis-based distributed rate limiter
async function checkRateLimit(userId: string): Promise<boolean> {
const key = `ratelimit:${userId}`;
const limit = 100;
const window = 60; // 60 seconds
const current = await redis.incr(key);
if (current === 1) {
await redis.expire(key, window);
}
if (current > limit) {
const ttl = await redis.ttl(key);
throw new RateLimitError({
retryAfter: ttl,
limit,
remaining: 0
});
}
return true;
}7.4 Rate Limiting Strategies
| Strategy | Description | Use Case |
|---|---|---|
| Per User | Limit by user ID | Most common |
| Per IP | Limit by client IP | Unauthenticated endpoints |
| Per API Key | Limit by key | B2B APIs |
| Per Endpoint | Different limits per route | Expensive operations |
| Global | Protect overall system | DDoS protection |
8. Backward Compatibility & Schema Evolution
8.1 Non-Breaking Changes
typescript
// ✅ SAFE: Adding optional field
// v1.0
interface UserV1 {
id: string;
name: string;
}
// v1.1 - Adding optional field (backward compatible)
interface UserV1_1 {
id: string;
name: string;
avatar?: string; // New optional field
}8.2 Deprecation Strategy
┌─────────────────────────────────────────────────────────────────────────┐
│ DEPRECATION TIMELINE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Month 0 Month 3 Month 6 Month 12 │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │v1 GA │───────►│v2 GA │───────►│v1 Dep│───────►│v1 EOL│ │
│ └──────┘ │v1 Dep│ │v2 GA │ │v2 GA │ │
│ │Warn │ │ │ │ │ │
│ └──────┘ └──────┘ └──────┘ │
│ │
│ Deprecation headers: │
│ Deprecation: Sun, 01 Jan 2025 00:00:00 GMT │
│ Sunset: Sun, 01 Jul 2025 00:00:00 GMT │
│ Link: <https://api.example.com/v2/docs>; rel="successor-version" │
│ │
└─────────────────────────────────────────────────────────────────────────┘8.3 Protobuf Schema Evolution Rules
| Change | Backward Compatible? | Forward Compatible? |
|---|---|---|
| Add optional field | ✅ Yes | ✅ Yes |
| Add required field | ❌ No | ❌ No |
| Remove field | ✅ Yes* | ❌ No |
| Change field type | ❌ No | ❌ No |
| Rename field | ✅ Yes (same number) | ✅ Yes |
| Change field number | ❌ No | ❌ No |
*Only if you never reuse the field number
9. Observability Hooks
9.1 Structured Request/Response Logging
typescript
// Middleware for structured logging
function observabilityMiddleware(req, res, next) {
const startTime = Date.now();
const traceId = req.headers['x-trace-id'] || generateTraceId();
// Inject trace ID into context
req.traceId = traceId;
res.setHeader('X-Trace-Id', traceId);
res.on('finish', () => {
const duration = Date.now() - startTime;
logger.info({
// Standard fields
timestamp: new Date().toISOString(),
traceId,
spanId: generateSpanId(),
// Request info
method: req.method,
path: req.path,
query: req.query,
userAgent: req.headers['user-agent'],
// Response info
statusCode: res.statusCode,
durationMs: duration,
// Custom dimensions
userId: req.user?.id,
apiVersion: req.headers['api-version'],
});
});
next();
}9.2 Metrics to Track
| Metric | Type | Description |
|---|---|---|
api_requests_total | Counter | Total requests by endpoint, method, status |
api_request_duration_ms | Histogram | Latency distribution |
api_active_requests | Gauge | Currently processing |
api_error_rate | Rate | Errors per second |
api_rate_limit_hits | Counter | Rate limit violations |
9.3 Distributed Tracing Headers
http
# W3C Trace Context (Standard)
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: vendor=value
# B3 (Zipkin)
X-B3-TraceId: 80f198ee56343ba864fe8b2a57d3eff7
X-B3-SpanId: e457b5a2e4d86bd1
X-B3-Sampled: 110. Anti-Patterns to Avoid
CAUTION
🔧 Kỹ sư Raizo: Những patterns này tôi đã thấy trong production và chúng luôn gây ra pain. Hãy tránh ngay từ đầu.
10.1 Common API Design Mistakes
┌─────────────────────────────────────────────────────────────────────────┐
│ ⚠️ API ANTI-PATTERNS │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ❌ 1. CHATTY APIs │
│ ────────────────────────────────────────────────────────────────────── │
│ // Bad: N+1 requests │
│ GET /orders → List order IDs │
│ GET /orders/1/items → Get items for order 1 │
│ GET /orders/2/items → Get items for order 2 │
│ ...repeat N times │
│ │
│ ✅ Fix: Compound documents or GraphQL │
│ GET /orders?include=items │
│ │
│ ────────────────────────────────────────────────────────────────────── │
│ ❌ 2. LEAKING INTERNAL IDs │
│ ────────────────────────────────────────────────────────────────────── │
│ // Bad: Exposes auto-increment IDs │
│ GET /users/1, /users/2, /users/3 │
│ → Attacker can enumerate all users │
│ │
│ ✅ Fix: Use UUIDs or public ID aliases │
│ GET /users/usr_abc123xyz │
│ │
│ ────────────────────────────────────────────────────────────────────── │
│ ❌ 3. VERB IN URL │
│ ────────────────────────────────────────────────────────────────────── │
│ // Bad: Actions as URLs │
│ POST /users/123/activate │
│ POST /orders/456/cancel │
│ │
│ ✅ Fix: Use resources and HTTP methods │
│ PATCH /users/123 { "status": "active" } │
│ DELETE /orders/456 │
│ │
│ ────────────────────────────────────────────────────────────────────── │
│ ❌ 4. INCONSISTENT NAMING │
│ ────────────────────────────────────────────────────────────────────── │
│ // Bad: Mixed conventions │
│ GET /users, /order-items, /ProductCategories │
│ │
│ ✅ Fix: Consistent kebab-case or snake_case │
│ GET /users, /order-items, /product-categories │
│ │
│ ────────────────────────────────────────────────────────────────────── │
│ ❌ 5. IGNORING FUTURE EXTENSIBILITY │
│ ────────────────────────────────────────────────────────────────────── │
│ // Bad: No envelope │
│ [{ "id": 1 }, { "id": 2 }] │
│ │
│ ✅ Fix: Response envelope │
│ { "data": [...], "meta": { "total": 100 } } │
│ │
└─────────────────────────────────────────────────────────────────────────┘10.2 Security Anti-Patterns
| Anti-Pattern | Risk | Fix |
|---|---|---|
| API keys in URL | Keys logged, cached | Use Authorization header |
| No rate limiting | DoS attacks | Implement per-user limits |
| Verbose errors in prod | Info disclosure | Generic errors + traceId |
| Missing input validation | Injection attacks | Validate + sanitize all input |
CORS * wildcard | CSRF attacks | Whitelist allowed origins |
📝 Summary
| Concept | Key Takeaway |
|---|---|
| REST vs GraphQL vs gRPC | REST for public, GraphQL for complex queries, gRPC for internal |
| Versioning | URL path versioning is most practical for most teams |
| Pagination | Use cursor-based for large datasets, offset for simple cases |
| Idempotency | Always require Idempotency-Key for POST requests |
| Error Model | RFC 7807 Problem Details + consistent error codes |
| Rate Limiting | Token bucket, always implement per-user + global limits |
| Backward Compat | Only add optional fields, deprecate with 6+ month notice |
| Observability | Trace ID in all requests, structured logging, standard metrics |
🚀 Next Steps
Related Topics
| Topic | Why |
|---|---|
| 📡 Sync Communication | Deep dive into HTTP/gRPC internals |
| 📬 Async Messaging | When APIs alone aren't enough |
| 🔄 Distributed Transactions | Saga pattern for cross-service operations |
Apply in Case Studies
| Case Study | API Learnings |
|---|---|
| 🐦 Design Twitter | Timeline API pagination |
| 💬 Design WhatsApp | Real-time message delivery |
| 🚗 Design Uber | Location streaming APIs |