API Design: Protocol Comparison
Decision Matrix
Protocol Transport Format Streaming Browser Best for
REST HTTP/1.1 JSON ❌ (SSE) ✅ Public APIs, CRUD, simple resources
GraphQL HTTP/1.1 JSON ✅ ✅ Flexible queries, BFF, multiple clients
gRPC HTTP/2 Protobuf ✅ (bi-di) ⚠️* Internal microservices, high-throughput
WebSockets TCP any ✅ bi-di ✅ Real-time: chat, live updates, gaming
* gRPC-Web proxy needed for browser support (Envoy / grpc-web package)REST
Strengths: simple, universal, human-readable, excellent tooling (curl, Postman, browsers), CDN-cacheable
Weaknesses: over-fetching (too much data) and under-fetching (N+1 requests), no built-in streaming
Use when: public API, CRUD operations, clients you don't control, CDN caching matters
Versioning needed for breaking changes; hard to evolve without breaking clients
GraphQL
# Client specifies exactly what fields it needs — no over/under-fetching
query GetUserWithPosts {
user(id: "123") {
name
email
posts(limit: 5) {
title
publishedAt
tags { name }
}
}
}
# Mutation
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
id
title
slug
}
}
# Subscription — real-time via WebSocket
subscription OnNewMessage($roomId: ID!) {
messageAdded(roomId: $roomId) {
id
text
author { name }
}
}Strengths: fetch exactly what you need, strongly typed schema, single endpoint, introspection
Weaknesses: N+1 queries (use DataLoader), complex caching, overkill for simple APIs, learning curve
Use when: BFF (Backend for Frontend), mobile clients needing bandwidth efficiency, multiple client types with different data needs
Tools: Apollo Server, Pothos (TypeScript), Strawberry (Python), Hot Chocolate (.NET)
gRPC
// Strongly typed contracts in .proto — code generated for all languages
service OrderService {
rpc CreateOrder (CreateOrderRequest) returns (Order); // unary
rpc StreamOrders (StreamRequest) returns (stream Order); // server streaming
rpc UploadItems (stream Item) returns (UploadResult); // client streaming
rpc Chat (stream Message) returns (stream Message); // bidirectional
}
message CreateOrderRequest {
string user_id = 1;
repeated OrderItem items = 2;
PaymentMethod payment_method = 3;
}
enum PaymentMethod {
PAYMENT_METHOD_UNSPECIFIED = 0;
CREDIT_CARD = 1;
PAYPAL = 2;
}Strengths: ~5x faster than JSON/REST (binary + HTTP/2 multiplexing), bidirectional streaming, generated type-safe clients for all languages
Weaknesses: binary format hard to debug without tooling, browser support limited (grpc-web proxy needed), no caching layer
Use when: internal microservice communication, high-throughput data pipelines, polyglot services that need type safety
Tools: grpcurl (like curl for gRPC), Kreya, BloomRPC, Buf (proto linting/breaking change detection)
WebSockets & SSE
// WebSocket — full-duplex, client and server can send at any time
const ws = new WebSocket('wss://api.example.com/ws')
ws.onopen = () => ws.send(JSON.stringify({ type: 'subscribe', channel: 'prices' }))
ws.onmessage = (e) => handleMessage(JSON.parse(e.data))
ws.onerror = console.error
ws.onclose = () => scheduleReconnect()
// Server-Sent Events (SSE) — server pushes only, simpler than WebSockets
// Great for: live feeds, notifications, progress updates
const evtSource = new EventSource('/api/notifications', { withCredentials: true })
evtSource.onmessage = (e) => showNotification(JSON.parse(e.data))
evtSource.addEventListener('order-update', (e) => updateOrder(JSON.parse(e.data)))
evtSource.onerror = () => evtSource.close()
// Server side (Node.js)
// res.setHeader('Content-Type', 'text/event-stream')
// res.setHeader('Cache-Control', 'no-cache')
// res.write('data: {"price": 42}\n\n') // double newline ends a message
// res.write('event: order-update\ndata: {"id": 1}\n\n')WebSockets: use for bidirectional real-time communication (chat, multiplayer, collaborative editing). Requires sticky sessions or pub/sub (Redis) when scaling.
SSE: simpler than WebSockets for server-push only. Automatic reconnect built-in. Works over HTTP/1.1. Better for notifications, live dashboards.
Long Polling: HTTP request held open until data available — fallback when WebSockets blocked. Poor performance at scale.
HTTP/2 Server Push: deprecated and removed from most browsers. Don't use.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free