WebSocket Fundamentals: A Practical Intro for Developers Building Real-Time Apps
A clear beginner's guide to WebSocket fundamentals—how the upgrade handshake works, when to use it, and how to send your first message in plain JavaScript.
You have built HTTP APIs before. Requests come in, responses go out. But now you need the server to push data to the client without the client asking first — a notification, a live price, a message from another user. HTTP was not designed for this. WebSockets were. This guide covers everything you need to understand before you write production code.
What a WebSocket Actually Is
A WebSocket is a persistent, full-duplex communication channel between a client and a server. "Full-duplex" means both sides can send messages independently and simultaneously, like a phone call rather than a walkie-talkie.
It starts as a standard HTTP request. The client sends an Upgrade: websocket header. If the server accepts, both sides switch to the WebSocket protocol over the same TCP connection. From that point on, the connection stays open until either side closes it, and either side can send a message at any time.
This is fundamentally different from standard HTTP, where:
- The client must always initiate
- The server sends exactly one response per request
- The connection closes after the response
With WebSockets, the server can push data whenever it wants, without any polling from the client.
The WebSocket Handshake
The upgrade happens through a standard HTTP GET request with specific headers:
GET /realtime HTTP/1.1
Host: api.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
The server responds with a 101 Switching Protocols status and a computed Sec-WebSocket-Accept header. After this exchange, the TCP connection is handed off to the WebSocket protocol. Standard HTTP proxies and firewalls generally handle this upgrade correctly on modern infrastructure, though some corporate environments require explicit allowlisting.
Sending Your First WebSocket Message in the Browser
The browser's built-in WebSocket API is straightforward:
const socket = new WebSocket('wss://api.example.com/realtime');
socket.addEventListener('open', () => {
console.log('Connected');
socket.send(JSON.stringify({ type: 'ping' }));
});
socket.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
});
socket.addEventListener('close', (event) => {
console.log('Connection closed:', event.code, event.reason);
});
socket.addEventListener('error', (error) => {
console.error('WebSocket error:', error);
});Use wss:// (WebSocket Secure, over TLS) in production, never plain ws://. The error event rarely carries useful detail on its own — the subsequent close event with its code and reason is more informative.
A Minimal Node.js WebSocket Server
Using the ws library, the server side is equally direct:
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (socket, request) => {
console.log('Client connected');
socket.on('message', (raw) => {
const msg = JSON.parse(raw);
console.log('Received:', msg);
socket.send(JSON.stringify({ type: 'pong' }));
});
socket.on('close', () => {
console.log('Client disconnected');
});
});The ws library is the most common low-level WebSocket library for Node.js. Socket.IO is a higher-level library built on top of it that adds rooms, namespaces, fallback transports, and automatic reconnection — worth considering once you understand what the lower layer is doing.
WebSocket Message Formats
WebSocket frames can carry text or binary data. Most application-level protocols use text frames with JSON payloads because JSON is easy to debug and universally supported. A common pattern is an envelope with a type field and a payload:
{
"type": "cursor_moved",
"payload": {
"userId": "u_123",
"x": 412,
"y": 207
}
}For high-throughput systems where payload size matters — game state, financial ticks — binary formats like MessagePack or Protocol Buffers over binary frames can cut bandwidth meaningfully. Start with JSON unless you have a measured reason to optimize.
Close Codes and What They Mean
When a WebSocket closes, it carries a numeric code. The most important ones:
| Code | Meaning |
|---|---|
| 1000 | Normal closure |
| 1001 | Going away (server shutting down) |
| 1006 | Abnormal closure (no close frame received) |
| 1011 | Internal server error |
| 1013 | Server at capacity |
| 4001 | Application-level: unauthorized (custom) |
Codes in the 4000-4999 range are reserved for application-defined use. Use them to communicate application-specific close reasons to the client.
What WebSockets Do Not Handle Automatically
Understanding the gaps is as important as the basics:
- Reconnection — the browser does not reconnect automatically on close; you must implement this yourself
- Heartbeats — dead connections are not surfaced immediately; you need ping/pong to detect them
- Message ordering guarantees — WebSockets are ordered within a connection, but if the client reconnects, it has no built-in mechanism to catch up on missed messages
- Authentication — the WebSocket handshake can carry cookies and headers, but most production systems implement an explicit auth frame after connection
These are the features you will need to build, or use a library that provides them.
When to Use WebSockets (and When Not To)
Use WebSockets for:
- Chat and messaging
- Live document collaboration
- Real-time dashboards where users also send commands
- Multiplayer features
Consider SSE or long polling instead for:
- Notification feeds (server-to-client only)
- Live data displays with no client-to-server interaction
- Deployments behind infrastructure that does not reliably support WebSocket upgrades
WebSockets are powerful but they come with operational complexity. Starting with a clear understanding of the protocol puts you in a much better position before you encounter the edge cases.
If you are designing a real-time product and want expert guidance on architecture from the start, talk to Clixo. We build real-time systems for product teams that want them done right.