Real-Time Basics
10 examples to choose between WebSocket, SSE, and long-polling - 7 basic and 3 intermediate.
Prerequisites
These examples assume Node.js 24.18.0 and an HTTP server already running (Express 5, Fastify 5, or raw node:http).
npm install ws
npm install -D @types/wsFor implementation details, see ws Library, Server-Sent Events, and Socket.IO.
Basic Examples
1. WebSocket vs SSE vs Long-Poll
| Protocol | Direction | Transport | Reconnect | Complexity |
|---|---|---|---|---|
| WebSocket | Bidirectional | TCP upgrade | Manual | Medium |
| SSE | Server to client | HTTP stream | Automatic | Low |
| Long-poll | Server to client | HTTP request | Manual | Lowest |
- Chat apps need WebSocket (client sends messages)
- Live dashboards need SSE (server pushes updates)
- Legacy browser support needs long-polling (rare in 2026)
2. When to Use WebSocket
Use WebSocket when:
- Client sends messages frequently (chat, gaming, collaboration)
- Latency must be < 50ms round-trip
- Bidirectional binary data (audio, file chunks)
- Connection state matters (presence, typing indicators)- WebSocket starts as HTTP upgrade, then switches to persistent TCP
- One connection handles both directions
- See ws Library for raw implementation
3. When to Use SSE
Use SSE when:
- Server pushes updates; client only sends via normal HTTP
- Live feeds (stock prices, sports scores, logs)
- Notification streams
- You want automatic browser reconnection// SSE response headers
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
res.write("data: {\"price\": 142.50}\n\n");- SSE is plain HTTP; works through most proxies and load balancers
- Browser
EventSourceAPI handles reconnection automatically
4. When to Use Long-Polling
Use long-polling when:
- Infrastructure blocks WebSocket (rare corporate proxies)
- Extremely simple notification need
- Fallback for Socket.IO (handled automatically)- Client sends HTTP request; server holds response until data arrives
- Higher latency and more HTTP overhead than SSE or WebSocket
- Generally avoid for new projects in 2026
5. Protocol Decision Matrix
| Use case | Best choice | Why |
|---|---|---|
| Chat application | WebSocket | Bidirectional, low latency |
| Live log viewer | SSE | Server push, auto-reconnect |
| Collaborative editing | WebSocket | Bidirectional state sync |
| Stock ticker | SSE | One-way push, HTTP-friendly |
| Multiplayer game | WebSocket | Bidirectional, binary support |
| Email notification badge | SSE or polling | Infrequent, one-way |
6. Connection Lifecycle
WebSocket lifecycle:
1. Client sends HTTP Upgrade request
2. Server responds 101 Switching Protocols
3. Persistent TCP connection established
4. Frames exchanged bidirectionally
5. Either side sends close frame
6. Connection terminated
SSE lifecycle:
1. Client opens HTTP GET (EventSource)
2. Server streams text/event-stream
3. Connection stays open
4. Server sends data: lines
5. On disconnect, browser auto-reconnects7. Auth on Connection
// WebSocket: validate during upgrade
server.on("upgrade", (req, socket, head) => {
const token = new URL(req.url!, "http://localhost").searchParams.get("token");
if (!isValid(token)) {
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
socket.destroy();
return;
}
wss.handleUpgrade(req, socket, head, (ws) => wss.emit("connection", ws, req));
});- Authenticate during the HTTP upgrade, before WebSocket frames flow
- Do not accept messages first and check auth later
- SSE auth: pass token in query param or cookie on the initial GET
Intermediate Examples
8. WebSocket with Express 5
import express from "express";
import { createServer } from "node:http";
import { WebSocketServer } from "ws";
const app = express();
const server = createServer(app);
const wss = new WebSocketServer({ noServer: true });
server.on("upgrade", (req, socket, head) => {
if (req.url?.startsWith("/ws")) {
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit("connection", ws, req);
});
}
});
wss.on("connection", (ws) => {
ws.on("message", (data) => ws.send(`echo: ${data}`));
});
server.listen(3000);- Attach WebSocket upgrade to the underlying HTTP server
- Express routes handle HTTP; upgrade handler manages WS
9. SSE with Fastify 5
import Fastify from "fastify";
const app = Fastify();
app.get("/events", async (req, reply) => {
reply.raw.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
const interval = setInterval(() => {
reply.raw.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`);
}, 1000);
req.raw.on("close", () => clearInterval(interval));
});- Use
reply.rawfor direct stream control in Fastify - Clean up intervals on client disconnect
10. Scaling Awareness
Before 1000 concurrent connections, plan for:
- Sticky sessions (load balancer affinity)
- Redis pub/sub for cross-instance messaging
- Connection limits per instance
- Heartbeat/ping to detect dead connections
- Graceful shutdown (notify clients, close connections)- Single Node process handles ~10k-50k idle WebSocket connections
- CPU becomes the limit with active message fanout
- See Scaling Real-Time
FAQs
WebSocket or SSE for a notification feed?
SSE. Notifications are server-to-client. SSE gives auto-reconnect and works through HTTP infrastructure.
Does SSE work with HTTP/2?
Yes. SSE works over HTTP/1.1 and HTTP/2. Disable proxy buffering in nginx.
When should I use Socket.IO instead of raw WebSocket?
When you need rooms, automatic fallback, and Redis adapter for scaling. See Socket.IO.
How many connections can one Node process handle?
10k-50k idle connections depending on memory. Active fanout reduces this. Profile with your message patterns.
Should I use WebSocket for REST API replacement?
No. Use HTTP REST for CRUD. Add WebSocket/SSE only for real-time push requirements.
Related
- ws Library - raw WebSocket implementation
- Server-Sent Events - SSE patterns
- Socket.IO - higher-level abstraction
- Scaling Real-Time - multi-instance scaling
- Real-Time Best Practices - section checklist
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.