Stack Patterns ■ Episode 07
Your frontend needs live data. A counter. A feed. A notification badge. The server knows when something changes. The client does not.
React taught an entire generation of developers, misleadingly, to poll.
useEffect(() => {
const id = setInterval(() => fetch('/api/count')
.then(r => r.json()).then(setCount), 3000);
return () => clearInterval(id);
}, []);
A hook. A cleanup. A state setter. An interval. Every three seconds: "Anything new?" No. "Anything new?" No. "Anything new?" "Yes, forty seconds ago."
Splendid timing.
The React ecosystem's answer:
npm install socket.io-client.
46 KB.
One install averaging 1,400 transitive dependencies from a
registry whose greatest contributions to the industry include
left-pad and event-stream. One does
hope yours are safe. The browser solved this in
2011
with precisely zero.
One would think someone might have mentioned that.
The Client
const ws = new WebSocket('ws://localhost:3000/ws');
ws.onmessage = e =>
document.querySelector('#count').textContent = e.data;
Two lines. No npm. No useEffect. No cleanup.
Native to every browser since
2011.
React arrived two years later and never mentioned it. Rather
an oversight for a 136 KB framework, that.
The Server
One function. One purpose. Push when state changes.
Rust (axum, two crate dependencies):
async fn push(mut ws: WebSocket) {
let mut n = 0u64;
loop {
n += 1;
if ws.send(Message::Text(n.to_string()))
.await.is_err() { break }
sleep(Duration::from_secs(1)).await;
}
}
Go (gorilla/websocket):
func push(conn *websocket.Conn) {
n := 0
for {
n++
conn.WriteMessage(websocket.TextMessage,
[]byte(strconv.Itoa(n)))
time.Sleep(time.Second)
}
}
Same client. Same protocol. One function does one thing. No npm on the server either.
PHP requires Swoole or ReactPHP. Persistent connections against a share-nothing architecture. Rather like fitting a sail to a submarine.
Why It Works
WebSocket is a persistent, full-duplex TCP channel. RFC 6455 defines the protocol: HTTP upgrades the connection once, then both sides send at will. Overhead per frame: two bytes. No library required on either side.
What This Replaces
useEffect + setInterval +
fetch: React's answer to real-time. N requests
per minute, N minus 1 return nothing.
Socket.IO: 46 KB client plus npm dependency chain. Marvellous for 10,000 concurrent chat users. Rather generous for a live counter.
Long polling: a 2006 workaround that somehow still has a job.
When to Use
Dashboards, notifications, tickers, build logs: anything where the server knows before the client does. When not: multiplayer games, collaborative editors. There, the frameworks earn their kilobytes.
The Point
Two lines of JavaScript. One function on the server. Zero npm packages. The browser has been ready since 2011.
Whenever you are.