C.W.K.
Stream
Lesson 04 of 05 · published

Where REST Ends and WebSocket Begins — The Handoff Decision

~10 min · streaming-async, websocket, rest-vs-ws

Level 0HTTP Newbie
0 XP0/46 lessons0/12 achievements
0/120 XP to next level120 XP to go0% complete
"WebSocket is not REST's replacement. It's the answer to one specific question REST can't answer well: 'I need both sides to send messages at any time.' Knowing where that line falls keeps you from over-engineering and from under-engineering."

The Spectrum from REST to WebSocket

From least to most real-time:

  1. REST request/response — client asks, server answers, done. Best for CRUD and ad-hoc queries.
  2. Polling — REST repeated. Tolerable latency, simple.
  3. Long-polling — REST that holds the response. Lower latency, more server cost per pending client.
  4. SSE — one-way streaming over HTTP. Server pushes; client receives. AI tokens, notifications, real-time logs.
  5. WebSocket — full-duplex persistent connection. Both sides send messages at any time, independent of each other.

Each step trades simplicity for capability. WebSocket is the most capable AND the most expensive operationally — choose it when you actually need it.

The Three Tests for Choosing WebSocket

1. Bidirectionality. Does the CLIENT also need to push messages to the server in real-time, beyond regular requests? Chat "is typing..." indicators, multiplayer game inputs, collaborative cursor positions — yes. AI chat (user sends one message, server streams a response, repeat) — no, SSE plus normal POST suffices.

2. Latency budget. Is your acceptable end-to-end latency below ~100ms? Trading platforms, live cursor tracking — yes. Most chat UI — no, SSE + POST is fine.

3. Message frequency. Are you sending many small messages per second per client? Game state updates at 30Hz — yes. Notifications a few per minute — no, SSE wins.

If you can answer NO to all three, REST + SSE is almost always the right choice. If you answer YES to any one, WebSocket starts looking attractive.

How WebSocket Sets Up Over HTTP

A WebSocket connection starts as a normal HTTP/1.1 request with an upgrade header. The server, if willing, responds with 101 Switching Protocols, after which the underlying TCP connection becomes a WebSocket frame stream:

GET /ws/chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

After the 101, both sides exchange WebSocket frames — small binary headers + payload. The connection stays open until either side closes it.

WebSocket pays its complexity cost in operational overhead — connection state, reconnection logic, scaling per-connection memory, monitoring active connections — to buy true bidirectionality. If you don't need both sides actively pushing, you don't need that complexity. Pick the lightest protocol that meets your real-time bar.

The Common Hybrid

Many production apps use both REST and WebSocket: REST for state management and queries (login, fetch history, save a draft), WebSocket for the live channel (chat messages, live edits, presence). The state and history live in REST-accessible resources; the WebSocket carries the events that mutate them. Each side updates the REST view; the WebSocket announces what changed for everyone listening.

cwkPippa's Hybrid

cwkPippa uses three patterns side by side: REST for conversation management (POST to create, GET to fetch, PUT to rename, DELETE to remove), SSE for AI token streaming (each /api/chat call streams tokens via Server-Sent Events), and WebSocket for the Cinder Sidekick channel (bidirectional control + state sync between the Photoshop UXP plugin and the cwkPippa backend, by way of Cinder). The WebSocket part exists because Cinder needs to push canvas state to cwkPippa AND receive Pippa's responses in the same channel — that's the bidirectionality test triggering it. If Cinder only needed one direction, SSE would suffice.

Code

FastAPI WebSocket: full bidirectional, broadcast to all connections·python
# FastAPI — a WebSocket endpoint (bidirectional)
from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()
_connections: set[WebSocket] = set()

@app.websocket('/ws/chat')
async def chat_socket(ws: WebSocket):
    await ws.accept()
    _connections.add(ws)
    try:
        while True:
            # Receive a message from the client (could be at any time)
            msg = await ws.receive_json()
            # Echo to all connected clients (broadcast)
            for conn in _connections:
                await conn.send_json({'from': msg.get('from'), 'text': msg.get('text')})
    except WebSocketDisconnect:
        _connections.discard(ws)
Browser WebSocket — full duplex, send/receive independently·javascript
// Browser client — WebSocket API (one line to open)
const ws = new WebSocket('ws://localhost:8000/ws/chat');

ws.onopen = () => console.log('connected');
ws.onmessage = (e) => {
  const data = JSON.parse(e.data);
  appendToChatUi(data);
};
ws.onclose = () => console.log('disconnected — implement reconnect logic');

// Send a message at any time, not tied to a request/response cycle
document.getElementById('send').onclick = () => {
  ws.send(JSON.stringify({ from: 'me', text: 'hi everyone' }));
};
Decision matrix — REST / SSE / WebSocket / polling·text
# Decision matrix — pick the right tool

| Need                                              | Pattern              |
|---------------------------------------------------|----------------------|
| Fetch / save / list — request/response            | REST                 |
| Server pushes events; client doesn't push back    | SSE                  |
| Latency tolerant (seconds-OK); push from server   | Polling / long-poll  |
| Both sides push messages at any time; low-latency | WebSocket            |
| Multiplayer game, live editor, trading            | WebSocket            |
| AI chat with streaming response                   | REST POST + SSE      |
| Notifications a few per minute                    | SSE (or long-poll)   |
| Live cursor tracking                              | WebSocket            |
| File download with progress events                | SSE for progress      |
| One-shot upload                                   | REST POST            |

External links

Exercise

Take a single feature you've thought about building (chat, notifications, multiplayer counter, real-time dashboard) and write down: (1) is it bidirectional in real-time, (2) what's the acceptable latency, (3) what's the expected message frequency. Then map to the decision matrix and pick REST / SSE / WebSocket. Defend the choice in two sentences. Bonus: implement two of the three patterns (e.g., SSE + WebSocket) and feel the operational difference.
Hint
Most 'real-time' features come out as SSE when you actually think about them. Real WebSocket use cases are narrower than the marketing suggests. The exercise teaches the discipline of picking by need, not by what sounds modern. The two-implementation bonus shows you the WebSocket complexity — reconnection logic, message ordering, connection state — that SSE just doesn't have.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.