API Design
04 / 05

GraphQL vs REST vs gRPC vs WebSockets

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