Connection lifecycle, server/client implementation, scaling strategies, and security.
Welcome everyone! Today we're diving deep into WebSockets — a technology that's transformed how we build real-time web applications. We'll explore what makes WebSockets different from traditional HTTP, when you should use them, and how to implement them correctly in production. By the end of this talk, you'll understand the persistent, full-duplex communication model and be ready to add real-time capabilities to your own applications. Let's get started.
So what exactly are WebSockets? At their core, they provide a persistent, bidirectional communication channel over a single TCP connection. This is fundamentally different from HTTP, where the client always has to initiate every request. Looking at the four cards here, we see the key advantages. First, full-duplex communication means both client and server can send messages simultaneously — no more request-response turns. Second, the persistent connection stays open after one TCP handshake, eliminating the overhead of repeated HTTP headers. Third, low latency — messages travel directly over the open connection with no DNS lookup or TLS negotiation per message. And finally, it's event-driven, so the server can push data the instant it's available, without any polling or wasted requests.
Let's compare HTTP and WebSockets side by side. Looking at this table, the differences become clear. HTTP creates a new connection for every request, while WebSocket uses a single persistent connection. HTTP is unidirectional — client to server only — whereas WebSocket is bidirectional. The overhead column is striking: HTTP sends around 800 bytes of headers with every request, but WebSocket frames are just 2 to 6 bytes. Latency-wise, HTTP requires a full round-trip per request, while WebSocket delivers near-instant messaging once connected. Now, HTTP has built-in caching, which WebSocket doesn't provide since it's designed for real-time data. The rule of thumb at the bottom says it all: if the server needs to push data to the client unprompted, use WebSockets. If the client only needs data when the user takes an action, stick with HTTP.
Let's walk through the WebSocket connection lifecycle using this sequence diagram. It starts as an HTTP request — the client sends an HTTP GET with an upgrade header requesting a WebSocket connection. The server responds with a 101 Switching Protocols status. This is the key moment — after that response, the TCP connection is upgraded and HTTP is done. Now the real magic happens. The client and server can send message frames back and forth. Notice the server can push events unprompted — it doesn't need to wait for the client to ask. You'll also see ping-pong frames for heartbeats, which keep the connection alive. Finally, when it's time to disconnect, the client sends a close frame with code 1000, the server acknowledges it, and the connection terminates cleanly. This upgrade mechanism is what makes WebSockets so efficient.
Now let's look at server-side code. This TypeScript example uses the ws library, which is the standard Node.js WebSocket server. At the top, we create a WebSocket server on port 8080 and maintain a set of connected clients. When a client connects, we add it to the set and log the connection. The interesting part is the message handler. When a client sends a message, we parse the JSON, then broadcast it to all other connected clients — notice we check if the client is not the sender and if the connection is still open before sending. The readyState check is crucial for preventing errors. Finally, when a client disconnects, we remove it from the set. The ws library handles the HTTP upgrade internally, so you don't have to worry about the protocol details. This gives you a working broadcast server in about 25 lines of code.
On the client side, reconnection logic is essential for production applications. This WebSocketClient class shows a robust pattern. We instantiate a WebSocket, set up event handlers, and most importantly, implement exponential backoff for reconnection. Looking at the onopen handler, we reset the reconnect attempts counter on successful connection. The onmessage handler parses incoming JSON and routes it to our application logic. Now the critical part — the onclose handler. If we get a clean close with code 1000, we don't reconnect. Otherwise, if we haven't exceeded max retries, we calculate an exponential backoff delay — 1 second, then 2, then 4, then 8, up to 16 seconds. The setTimeout schedules the reconnection attempt. This prevents overwhelming the server during outages. The send method at the bottom checks readyState before sending to avoid errors on closed connections.
Why is exponential backoff so important? When a server goes down, all clients disconnect at once. If they all try to reconnect immediately, you get a thundering herd problem — the server is hammered with connection requests the instant it restarts. The solution is exponential backoff with jitter. Looking at the code, we calculate a base delay that doubles each attempt — 1 second, 2, 4, 8 — then add random jitter between 0 and 1000 milliseconds, capping at 30 seconds. The table shows this in action. Attempt zero might reconnect at 1.4 seconds, attempt one at 2.7 seconds, and so on. The jitter is key — it ensures clients don't all reconnect at exactly the same moment. This spreads the load and gives your server breathing room to recover gracefully.
In real applications, you rarely want to broadcast to everyone. Rooms let you scope messages to relevant clients. Looking at this code, we use a Map where each room ID points to a Set of WebSocket clients. The joinRoom function adds a client to a room, creating the Set if it doesn't exist. The leaveRoom function removes the client and cleans up empty rooms. The broadcastToRoom function is where it gets useful — it looks up the room members, serializes the message once, then sends it only to clients in that room, optionally excluding the sender. Notice we check readyState before sending. At the bottom, you can see example usage — a client joins a chat room, then we broadcast a message to just that room. This pattern is essential for chat apps, collaborative tools, or any scenario where you have logical groups of users.
Raw WebSockets don't enforce any message format — you need to design your own application-level protocol. This code shows a typed approach using TypeScript discriminated unions. We define ClientMessage types for actions users can take: join a room, leave, send a chat message, or indicate they're typing. Then we define ServerMessage types for responses: confirmation of joining, chat messages from other users, typing indicators, and errors. Every message has a type field for routing. Looking at the chat message type, we include the room ID, the sender, the text, and a timestamp. The tips below emphasize this — always include a type field, add timestamps for proper ordering, and define error types so clients can handle failures gracefully. This kind of structure makes your WebSocket application predictable and maintainable.
Security is critical with WebSockets since they bypass many traditional HTTP protections. Looking at these four cards, let's cover the essentials. First, authentication — you must validate auth tokens during the HTTP upgrade, before the WebSocket opens. Pass tokens as query parameters or in the first message after connecting. Second, rate limiting — a malicious client can flood your server with messages, so cap each client at something like 30 messages per second. Third, input validation — parse and validate every incoming message. Reject malformed JSON, oversized payloads, and unexpected message types. And fourth, origin checking — verify the Origin header during the upgrade to prevent cross-site WebSocket hijacking, or CSWSH. This is similar to CSRF protection. Without these controls, your WebSocket server becomes a security liability.
Where do WebSockets shine in production? Looking at these four real-world use cases, first is live chat — real-time messaging with typing indicators and read receipts, where each conversation is a room. Second, collaborative editing — think Google Docs where multiple people edit simultaneously. You'd use Operational Transform or CRDTs to resolve conflicts in real time. Third, financial tickers — streaming stock prices and order book updates where clients subscribe to specific symbols they care about. And fourth, live dashboards — pushing metrics, alerts, and log streams to monitoring dashboards without any polling. In each case, the server needs to push data unprompted, and latency matters. These are perfect WebSocket scenarios where HTTP polling would be inefficient or create too much lag.
WebSockets are powerful but not always the right choice. Let's look at this table of scenarios where simpler alternatives work better. If you only need infrequent updates — say every 30 seconds or more — HTTP polling is simpler, cacheable, and stateless. If it's server to client only with no client messages, Server-Sent Events, or SSE, gives you a simpler API with auto-reconnect and better proxy compatibility. For classic request-response patterns, stick with REST or GraphQL — you get HTTP caching, status codes, and better tooling. Behind corporate proxies, use SSE or long polling since some proxies actively block WebSocket upgrades. And for static content delivery, a CDN with HTTP is the answer — WebSockets can't be cached at the edge. The bottom note is important — Server-Sent Events handle about 80% of real-time use cases like notifications, feed updates, and progress bars, with far less complexity than WebSockets.
Let's wrap up with testing. Looking at this terminal output, wscat is the WebSocket equivalent of curl — it's essential for debugging. After installing it globally with npm, you can connect to any WebSocket server. In this example, we connect to localhost on port 8080. We send a join message for the general room, and the server responds with a joined confirmation showing current members alice and bob. Then we send a chat message, and the server broadcasts bob's response with a timestamp. Finally, we send a leave message and disconnect cleanly with code 1000. For automated testing, use the ws library in your test suite to open real WebSocket connections against your server. This lets you verify message handling, reconnection logic, and error cases in a controlled environment. Manual testing with wscat plus automated tests gives you confidence your WebSocket implementation works correctly.
Hands-on implementation guides with detailed code examples, step-by-step instructions, and expanded explanations for each topic.