About This Page

Covers API design and networking — protocols, real-time communication, DNS, CDN, and rate limiting. Parent: System Design. See also: System Design - Microservices, System Design - Scalability & CAP.

API Design Protocols

REST vs GraphQL vs gRPC

AspectRESTGraphQLgRPC
ProtocolHTTP/1.1–2HTTPHTTP/2
FormatJSON / XMLJSONProtocol Buffers (binary)
Type safety❌ No schema✅ Schema required✅ .proto schema
Over/under-fetch❌ Common issue✅ Fetch exactly what you need✅ Defined contracts
Caching✅ HTTP cache works❌ Complex (POST-based)❌ No native
Streaming❌ No✅ Subscriptions✅ Bidirectional streaming
Browser support✅ Native✅ Native❌ Limited (need grpc-web)
Code generationManualCodegen tools✅ First-class (protoc)
Best forPublic APIs, CRUDMobile apps, BFF patternInternal microservices

REST API Design Best Practices

Resources are nouns, not verbs:
  ✅ GET  /users/123
  ❌ GET  /getUser?id=123

Use correct HTTP methods:
  GET     → Read (idempotent, cacheable)
  POST    → Create (not idempotent)
  PUT     → Replace entire resource (idempotent)
  PATCH   → Partial update (idempotent)
  DELETE  → Delete (idempotent)

Use standard status codes:
  200 OK           → GET, PUT success
  201 Created      → POST success (include Location header)
  204 No Content   → DELETE success
  400 Bad Request  → Invalid input
  401 Unauthorized → Not authenticated
  403 Forbidden    → Authenticated but no permission
  404 Not Found    → Resource doesn't exist
  409 Conflict     → Duplicate / state conflict
  422 Unprocessable→ Validation failed
  429 Too Many Req → Rate limited
  500 Internal Err → Server bug

Versioning:
  URL path:    /api/v1/users  (simplest, most visible)
  Header:      Accept: application/vnd.api.v1+json
  Query param: /users?version=1

gRPC Deep Dive

// user.proto — define service contract
syntax = "proto3";
 
service UserService {
  rpc GetUser    (GetUserRequest)    returns (User);
  rpc ListUsers  (ListUsersRequest)  returns (stream User);  // server streaming
  rpc CreateUser (CreateUserRequest) returns (User);
}
 
message GetUserRequest { string user_id = 1; }
 
message User {
  string id    = 1;
  string name  = 2;
  string email = 3;
  int64  created_at = 4;
}
gRPC streaming types:
  Unary:              Client sends 1 request → Server sends 1 response
  Server streaming:   Client sends 1 → Server sends stream
  Client streaming:   Client sends stream → Server sends 1
  Bidirectional:      Both stream simultaneously (chat, gaming)

Real-Time Communication

Comparison

MethodDirectionConnectionOverheadWhen to Use
Short PollingC→S (repeated)New each timeHighSimple, infrequent updates
Long PollingC→S (hold)Held openMediumNear real-time, simple setup
WebSocketsBidirectionalPersistentLow (after handshake)Chat, gaming, trading, collaboration
SSES→C onlyPersistentLowDashboards, feeds, notifications

WebSocket Flow

1. Client sends HTTP Upgrade request:
   GET /chat HTTP/1.1
   Upgrade: websocket
   Connection: Upgrade
   Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

2. Server responds:
   HTTP/1.1 101 Switching Protocols
   Upgrade: websocket
   Connection: Upgrade
   Sec-WebSocket-Accept: <hash>

3. Persistent TCP connection — both sides can send at any time

Scaling WebSockets:
  → Sticky sessions (same client → same server)
  → Or: all servers connect to shared pub/sub (Redis Pub/Sub)
  → Client disconnects stored in shared state (Redis)

SSE (Server-Sent Events)

// Server (Node.js)
app.get('/events', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
 
  const send = () => res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`);
  const interval = setInterval(send, 1000);
  req.on('close', () => clearInterval(interval));
});
 
// Client
const es = new EventSource('/events');
es.onmessage = (e) => console.log(JSON.parse(e.data));
// Auto-reconnects on disconnect — built into browser!

DNS & CDN

DNS Resolution Flow

flowchart LR
    Browser --> BCache[Browser Cache]
    BCache -->|miss| OS[OS Cache]
    OS -->|miss| Resolver[Recursive Resolver\nISP / 8.8.8.8]
    Resolver -->|miss| Root[Root Nameserver]
    Root --> TLD[TLD Nameserver .com]
    TLD --> Auth[Authoritative NS]
    Auth --> IP[IP Address ✅]
DNS RecordMapsExample
ADomain → IPv4example.com → 93.184.216.34
AAAADomain → IPv6example.com → 2606:2800::1
CNAMEAlias → Domainwww → example.com
MXMail serverpriority + mail host
TXTArbitrary textSPF, DKIM, domain verification
NSNameserver for domainns1.example.com

CDN Architecture

Without CDN:
  User in Mumbai → Server in Virginia (200ms latency)

With CDN:
  User in Mumbai → CDN edge in Mumbai (10ms latency)
  Cache miss → CDN fetches from origin, caches at edge

CDN caches:
  ✅ Static: JS, CSS, images, videos, fonts
  ✅ Dynamic (some CDNs): edge computing, personalised content

Providers: Cloudflare, AWS CloudFront, Akamai, Fastly, Azure CDN

Cache invalidation:
  Versioned filenames: main.abc123.js (change content = new URL)
  CDN purge API: invalidate specific paths on deploy
  Short TTL + stale-while-revalidate for dynamic content

Rate Limiting

Algorithms

AlgorithmBurst AllowedAccuracyMemoryBest For
Token Bucket✅ YesHighLowAPIs that allow short bursts
Leaky Bucket❌ SmoothedHighLowSmooth output rate
Fixed Window✅ at edgesLow (boundary bug)Very LowSimple, approximate
Sliding Window LogPartialVery HighHighStrict accuracy
Sliding Window CounterPartialHighLowBalanced accuracy/memory

Token Bucket Explained

Bucket capacity: 10 tokens
Refill rate:      2 tokens/second
Request cost:     1 token

t=0:  10 tokens, 5 requests → 5 tokens remain
t=1:  7 tokens (refilled 2), 3 requests → 4 tokens
t=2:  6 tokens, 10 requests → 6 allowed, 4 REJECTED (429)

Allows burst up to bucket size, then throttles to refill rate.

Redis Rate Limiter (Sliding Window)

-- Lua script for atomic sliding window rate limit
local key       = KEYS[1]         -- e.g. "ratelimit:user:123"
local limit     = tonumber(ARGV[1]) -- e.g. 100
local window_ms = tonumber(ARGV[2]) -- e.g. 60000 (1 minute)
local now       = tonumber(ARGV[3]) -- current timestamp ms
 
-- Remove entries outside the window
redis.call('ZREMRANGEBYSCORE', key, 0, now - window_ms)
 
-- Count entries in window
local count = redis.call('ZCARD', key)
 
if count < limit then
  -- Add this request
  redis.call('ZADD', key, now, now)
  redis.call('PEXPIRE', key, window_ms)
  return 1   -- allowed
end
 
return 0      -- rejected

Rate Limit Response Headers

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1715500000
Retry-After: 60
Content-Type: application/json
 
{"error": "rate_limit_exceeded", "retry_after_seconds": 60}

HTTP Versions

FeatureHTTP/1.1HTTP/2HTTP/3
TransportTCPTCPQUIC (UDP)
Multiplexing❌ One request at a time✅ Multiple streams✅ Multiple streams
Header compression✅ HPACK✅ QPACK
Head-of-line blocking✅ (TCP + app level)✅ (TCP level)❌ Eliminated
Server push
Connection setup1-RTT1-RTT0-RTT possible
Best forLegacy systemsMost modern appsMobile, lossy networks

Security in APIs

Auth Flow Comparison

MethodHowStateful?Best For
Session + CookieServer stores session✅ YesTraditional web apps
JWTSigned token, client stores❌ NoSPAs, mobile, microservices
OAuth 2.0Delegate to identity provider“Login with Google”
API KeyStatic secret in headerServer-to-server, developer APIs
mTLSMutual certificatesMicroservice-to-microservice

JWT Structure

JWT = header.payload.signature

Header:    { "alg": "HS256", "typ": "JWT" }
Payload:   { "sub": "user123", "role": "admin", "exp": 1715500000 }
Signature: HMACSHA256(base64(header) + "." + base64(payload), secret)

Flow:
  1. User logs in → server creates JWT → returns to client
  2. Client stores JWT (httpOnly cookie recommended)
  3. Every request: Authorization: Bearer <token>
  4. Server verifies signature → extracts claims → authorizes

Best practices:
  ✅ Short expiry (15 min access token)
  ✅ Refresh tokens (7 days, rotated)
  ✅ Store in httpOnly cookie (not localStorage)
  ✅ Use RS256 (asymmetric) for distributed verification

Useful Links & Resources