Day 43: Network Layer Architecture: REST, GraphQL, and WebSockets
Choose the right network paradigm per use case by understanding the real tradeoffs — request shape, over/under-fetching, and connection lifetime.
Study
Concepts
REST vs GraphQL: the over-fetching / under-fetching tradeoff
REST exposes fixed-shape resources per endpoint (`GET /users/1`, `GET /users/1/posts`) — simple to cache (HTTP caching works out of the box, Day 44), simple to reason about, but prone to OVER-fetching (an endpoint returns fields you don't need) or UNDER-fetching (you need data from 3 endpoints, requiring 3 round trips, or a waterfall of dependent requests). GraphQL exposes one endpoint with a typed schema; the CLIENT specifies exactly which fields it needs across related resources in a single query, eliminating both over- and under-fetching at the cost of losing free HTTP caching (most GraphQL traffic is a POST to one URL) and needing more server-side query-cost governance (a malicious or accidental deep/broad query can be expensive to resolve).
The practical decision: REST fits well when resource shapes are stable and consumers are relatively uniform (a small number of client apps you control); GraphQL earns its complexity when many different clients (web, iOS, Android, third parties) need different slices of overlapping data, or when a screen naturally needs deeply nested/related data in one round trip.
WebSockets: when the server needs to push, not just respond
REST and GraphQL (over HTTP) are fundamentally request-response: the client always initiates. WebSockets establish a single persistent, full-duplex connection where EITHER side can send a message at any time — the correct choice when the server needs to proactively push updates (chat messages, live notifications, collaborative cursors, live prices) rather than the client repeatedly asking "anything new?" via polling.
The real cost of WebSockets is operational: connections are stateful and long-lived, so load balancing needs sticky sessions or a shared pub/sub backplane (Redis, etc.) across server instances, reconnection/backoff logic is the client's responsibility, and you generally still need a REST/GraphQL API alongside it for anything that is naturally request-response (initial data load, mutations) — WebSockets typically augment rather than replace the other two.
See It
Visualizations
Visualization
REST vs GraphQL vs WebSockets
| REST | GraphQL | |
|---|---|---|
| Endpoint shape | Many endpoints, fixed response shape | One endpoint, client-specified shape |
| Over/under-fetching | Common problem | Solved by design |
| HTTP caching | Free, works out of the box | Mostly lost (POST-based) |
| Best for | Stable resources, few client types | Many client types, nested/related data needs |
| Connection model | Request-response | Request-response |
Visualization
Polling vs WebSocket push for "new chat message"
Polling
Client asks every 3s: "anything new?"
wasted requests when idle, up to 3s latency
WebSocket
Server pushes the instant a message arrives
near-zero latency, no wasted idle requests
Build It
Code Examples
REST under-fetching vs one GraphQL query
// REST: 3 round trips (or a hand-rolled aggregation endpoint) to
// render a profile page needing user + their posts + follower count
const user = await fetch('/api/users/1').then((r) => r.json());
const posts = await fetch('/api/users/1/posts').then((r) => r.json());
const followers = await fetch('/api/users/1/followers/count').then((r) => r.json());
// GraphQL: ONE round trip, client-specified shape, nothing extra
const query = `
query ProfilePage($id: ID!) {
user(id: $id) {
name
avatarUrl
posts(limit: 5) { id title }
followerCount
}
}
`;
const { data } = await graphqlClient.request(query, { id: '1' });A minimal WebSocket client with reconnect backoff
function connectChatSocket(roomId, onMessage) {
let attempt = 0;
let socket;
function connect() {
socket = new WebSocket(`wss://chat.example.com/rooms/${roomId}`);
socket.onmessage = (event) => onMessage(JSON.parse(event.data));
socket.onclose = () => {
const delay = Math.min(1000 * 2 ** attempt, 30_000); // exponential backoff, capped
attempt++;
setTimeout(connect, delay); // client owns reconnection — the server won't retry for you
};
socket.onopen = () => { attempt = 0; }; // reset backoff on a successful connection
}
connect();
return () => socket.close();
}Remember
Key Takeaways
- REST: many fixed-shape endpoints, free HTTP caching, prone to over/under-fetching for complex screens.
- GraphQL: one typed endpoint, client-specified shape, solves fetching-shape problems but mostly loses HTTP caching.
- WebSockets are for server-initiated push (chat, live data) — a persistent, full-duplex, stateful connection, not a REST/GraphQL replacement.
- WebSocket infrastructure needs sticky sessions or a shared pub/sub backplane across server instances, plus client-owned reconnect logic.
- Most real production systems combine all three: REST/GraphQL for request-response, WebSockets layered on top for live push.
Do It
Practice
- 1Design the API shape (REST or GraphQL, and why) for a hypothetical e-commerce product page needing product, reviews, and related items.
- 2Implement the reconnect-with-backoff WebSocket client above against a public echo WebSocket server and verify it reconnects after a forced disconnect.
- 3Write a one-paragraph justification for when you would add WebSockets to an existing REST API, using a concrete feature example.