Websocket APIs — Real-Time Communication Guide
WebSocket is a full-duplex communication protocol that enables real-time, bidirectional data transfer between clients and servers over a single, long-lived TCP connection, unlike HTTP request-response cycles.
What You'll Learn
You will learn how WebSockets work, implement a WebSocket server with Node.js and Python, handle broadcasting and rooms, manage reconnection and heartbeats, and build a real-time chat application.
Why WebSocket APIs Matter
HTTP was designed for document retrieval, not real-time communication. Polling and long-polling are inefficient workarounds. WebSocket reduces latency from seconds to milliseconds, cuts bandwidth by eliminating HTTP headers on every message, and enables use cases that HTTP cannot support: live chat, real-time notifications, collaborative editing, live streaming data, and multiplayer gaming.
Real-World Use
DodaTech uses WebSockets across products. Doda Browser sync uses WebSocket for real-time bookmark synchronization across devices, DodaZIP uses WebSocket for live download progress updates, and Durga Antivirus Pro uses WebSocket to push real-time threat alerts to dashboards.
WebSocket APIs Learning Path
flowchart LR
A[HTTP Basics] --> B[Websocket Protocol]
B --> C[Handshake & Upgrade]
C --> D[Server Implementation]
D --> E[Client Connection]
E --> F[Rooms & Broadcasting]
F --> G[Reconnection & Scaling]
B:::current
classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px
Prerequisites
Understand HTTP Protocol Basics and RESTful Api Design Best Practices. Familiarity with JavaScript Basics or Python Basics is required for implementation examples.
How WebSocket Works
WebSocket starts as an HTTP request and upgrades to the WebSocket protocol through a handshake.
The Handshake
Client sends:
GET /ws HTTP/1.1
Host: API.dodatech.com
Upgrade: Websocket
Connection: Upgrade
Sec-Websocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-<a href="/apis/Websocket/">Websocket</a>-Version: 13
Server responds:
HTTP/1.1 101 Switching Protocols
Upgrade: <a href="/apis/Websocket/">Websocket</a>
Connection: Upgrade
Sec-<a href="/apis/Websocket/">Websocket</a>-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
After the handshake, the connection switches from HTTP to WebSocket protocol. Both client and server can send messages at any time.
Message Frame
WebSocket messages are sent in frames:
| Bit | 0-1 | 2-3 | 4 | 5-9 | 10-14 | 15+ |
|---|---|---|---|---|---|---|
| Field | FIN | Opcode | Mask | Payload Length | Masking Key | Payload Data |
- FIN: Final frame indicator
- Opcode: 1 = text, 2 = binary, 8 = close, 9 = ping, 10 = pong
- Mask: Client-to-server frames must be masked
WebSocket Server Implementation
Node.js with ws Library
// server.js
const Websocket = require("ws");
const HTTP = require("HTTP");
const Express = require("Express");
const app = Express();
const server = HTTP.createServer(app);
const wss = new Websocket.Server({ server });
// Track connected clients
const clients = new Map();
wss.on("connection", (ws, req) => {
const clientId = Date.now();
clients.set(clientId, ws);
console.log(`Client ${clientId} connected`);
// Send welcome message
ws.send(JSON.stringify({
type: "welcome",
clientId,
message: "Connected to DodaTech Websocket server"
}));
// Handle incoming messages
ws.on("message", (data) => {
try {
const message = JSON.parse(data);
console.log(`Received from ${clientId}:`, message);
// Broadcast to all clients
broadcast({
type: "message",
clientId,
content: message.content,
timestamp: new Date().toISOString()
}, clientId);
} catch (err) {
ws.send(JSON.stringify({
type: "error",
message: "Invalid JSON format"
}));
}
});
// Handle client disconnect
ws.on("close", () => {
console.log(`Client ${clientId} disconnected`);
clients.delete(clientId);
broadcast({
type: "user-left",
clientId,
timestamp: new Date().toISOString()
});
});
// Handle errors
ws.on("error", (err) => {
console.error(`Client ${clientId} error:`, err);
clients.delete(clientId);
});
});
// Broadcast message to all connected clients
function broadcast(message, excludeClientId = null) {
const data = JSON.stringify(message);
clients.forEach((client, id) => {
if (id !== excludeClientId && client.readyState === Websocket.OPEN) {
client.send(data);
}
});
}
// Heartbeat to detect stale connections
setInterval(() => {
clients.forEach((client, id) => {
if (client.readyState === <a href="/apis/Websocket/">Websocket</a>.OPEN) {
client.ping();
} else {
clients.delete(id);
}
});
}, 30000);
server.listen(3000, () => {
console.log("Websocket server running on ws://localhost:3000");
});
Python with websockets Library
# server.py
import asyncio
import JSON
import websockets
connected_clients = set()
async def handler(Websocket, path):
client_id = id(Websocket)
connected_clients.add(Websocket)
print(f"Client {client_id} connected")
try:
# Send welcome message
await Websocket.send(JSON.dumps({
"type": "welcome",
"clientId": client_id,
"message": "Connected to DodaTech Websocket server"
}))
# Handle incoming messages
async for message in Websocket:
data = JSON.loads(message)
print(f"Received from {client_id}: {data}")
# Broadcast to all clients
broadcast_message = JSON.dumps({
"type": "message",
"clientId": client_id,
"content": data["content"],
"timestamp": "2026-06-23T10:00:00Z"
})
websockets.broadcast(connected_clients, broadcast_message)
except websockets.exceptions.ConnectionClosed:
print(f"Client {client_id} disconnected")
finally:
connected_clients.discard(<a href="/apis/Websocket/">Websocket</a>)
async def main():
async with websockets.serve(handler, "localhost", 3000):
print("Websocket server running on ws://localhost:3000")
await asyncio.Future()
asyncio.run(main())
WebSocket Client Implementation
Browser Client
// client.js
const ws = new Websocket("ws://localhost:3000");
// Connection opened
ws.addEventListener("open", (event) => {
console.log("Connected to Websocket server");
// Send a message
ws.send(JSON.stringify({
content: "Hello from Doda Browser!"
}));
});
// Listen for messages
ws.addEventListener("message", (event) => {
const data = JSON.parse(event.data);
console.log("Received:", data);
switch (data.type) {
case "welcome":
console.log(`Connected as client ${data.clientId}`);
break;
case "message":
displayMessage(data);
break;
case "error":
console.error("Server error:", data.message);
break;
case "user-left":
console.log(`User ${data.clientId} left`);
break;
}
});
// Handle errors
ws.addEventListener("error", (event) => {
console.error("Websocket error:", event);
});
// Handle connection close
ws.addEventListener("close", (event) => {
console.log("Disconnected from server");
attemptReconnect();
});
function displayMessage(data) {
const chat = document.getElementById("chat");
const msg = document.createElement("div");
msg.textContent = `${data.clientId}: ${data.content}`;
chat.appendChild(msg);
}
Reconnection with Exponential Backoff
class WebSocketClient {
constructor(URL) {
this.URL = URL;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 10;
this.baseDelay = 1000;
this.ws = null;
}
connect() {
this.ws = new Websocket(this.URL);
this.ws.onopen = () => {
console.log("Connected");
this.reconnectAttempts = 0;
};
this.ws.onclose = () => {
this.scheduleReconnect();
};
this.ws.onerror = () => {
// onclose will fire after onerror
};
}
scheduleReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error("Max reconnect attempts reached");
return;
}
const delay = this.baseDelay * Math.pow(2, this.reconnectAttempts);
// Add jitter: randomize delay by +/- 50%
const jitter = delay * (0.5 + Math.random());
console.log(`Reconnecting in ${Math.round(jitter)}ms (attempt ${this.reconnectAttempts + 1})`);
setTimeout(() => {
this.reconnectAttempts++;
this.connect();
}, jitter);
}
send(data) {
if (this.ws && this.ws.readyState === Websocket.OPEN) {
this.ws.send(data);
} else {
console.error("Not connected");
}
}
}
Rooms and Namespaces
For larger applications, organize connections into rooms.
// rooms.js
const rooms = new Map();
wss.on("connection", (ws, req) => {
ws.joinRoom = (roomName) => {
if (!rooms.has(roomName)) {
rooms.set(roomName, new Set());
}
rooms.get(roomName).add(ws);
ws.currentRoom = roomName;
};
ws.leaveRoom = () => {
if (ws.currentRoom && rooms.has(ws.currentRoom)) {
rooms.get(ws.currentRoom).delete(ws);
ws.currentRoom = null;
}
};
// Handle message with room destination
ws.on("message", (data) => {
const message = JSON.parse(data);
if (message.action === "join") {
ws.leaveRoom();
ws.joinRoom(message.room);
ws.send(JSON.stringify({
type: "room-joined",
room: message.room
}));
return;
}
if (message.room) {
// Send to specific room
broadcastToRoom(message.room, {
client: getId(ws),
content: message.content,
room: message.room
}, ws);
}
});
ws.on("close", () => {
ws.leaveRoom();
});
});
function broadcastToRoom(roomName, message, excludeWs = null) {
const room = rooms.get(roomName);
if (!room) return;
const data = JSON.stringify(message);
room.forEach((client) => {
if (client !== excludeWs && client.readyState === <a href="/apis/Websocket/">Websocket</a>.OPEN) {
client.send(data);
}
});
}
Common Errors
Not handling connection drops — WebSocket connections drop frequently due to network issues, proxies, and load balancers. Always implement reconnection logic with exponential backoff.
No heartbeat mechanism — Connections appear open but are actually dead. Implement ping/pong heartbeats to detect and clean up stale connections every 30 seconds.
Sending binary as text — Binary data sent as UTF-8 text causes corruption. Use the correct WebSocket frame type (opcode 2 for binary, opcode 1 for text).
Memory leaks from unclosed connections — WebSocket connections that are not properly cleaned up accumulate in server memory. Always remove disconnected clients from tracking maps and rooms.
Broadcasting to disconnected clients — Trying to send to a client whose connection has closed but is still in the tracking map. Check
readyState === <a href="/apis/websocket/">WebSocket</a>.OPENbefore sending.Not validating input — Receiving messages from clients without validation. Malicious clients can send malformed data that crashes the server. Validate and sanitize all incoming messages.
Scaling across multiple servers — WebSocket connections are tied to a specific server instance. Broadcasting to all clients requires a pub/sub system like Redis. Use Redis Pub/Sub to relay messages between server instances.
Practice Questions
- How does the WebSocket handshake upgrade from HTTP?
- What is the difference between WebSocket and HTTP long-polling?
- Why do WebSocket clients need reconnection logic?
- How does a heartbeat mechanism work in WebSocket connections?
- How do you scale WebSocket connections across multiple servers?
Challenge
Build a real-time collaborative document editing system using WebSockets. Features include: multiple users can edit the same document simultaneously, changes are broadcast to all connected clients, each user has a Cursor color visible to others, reconnection with State recovery (client receives missed changes on reconnect), and rooms for different documents. Use operational transformation or CRDT for Conflict Resolution.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro