Skip to content

Websocket APIs — Real-Time Communication Guide

DodaTech Updated 2026-06-23 8 min read

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

Npm init -y
Npm install ws Express
// 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

  1. Not handling connection dropsWebSocket connections drop frequently due to network issues, proxies, and load balancers. Always implement reconnection logic with exponential backoff.

  2. No heartbeat mechanism — Connections appear open but are actually dead. Implement ping/pong heartbeats to detect and clean up stale connections every 30 seconds.

  3. 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).

  4. Memory leaks from unclosed connectionsWebSocket connections that are not properly cleaned up accumulate in server memory. Always remove disconnected clients from tracking maps and rooms.

  5. 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>.OPEN before sending.

  6. 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.

  7. Scaling across multiple serversWebSocket 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

  1. How does the WebSocket handshake upgrade from HTTP?
  2. What is the difference between WebSocket and HTTP long-polling?
  3. Why do WebSocket clients need reconnection logic?
  4. How does a heartbeat mechanism work in WebSocket connections?
  5. 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

What is the maximum size of a WebSocket message? The WebSocket protocol supports messages up to 2^63 bytes. In practice, limit messages to 256KB to avoid fragmenting the frame and causing performance issues. For larger payloads, use streaming or chunking.

Can WebSocket work through firewalls and proxies? Yes. WebSocket uses port 80 (ws) or 443 (wss same as HTTPS). Most firewalls and proxies support WebSocket through HTTP/1.1 upgrade mechanism. Some proxies may close idle connections, so heartbeats are important.

What is the difference between WebSocket and Server-Sent Events (SSE)? WebSocket is full-duplex (both client and server can send). SSE is server-to-client only (unidirectional). WebSocket supports binary data. SSE is text-only and uses standard HTTP. Use SSE for simple push notifications and WebSocket for interactive applications.

How do I authenticate WebSocket connections? Pass an authentication token as a query parameter or in the first message after connection. Validate the token during the upgrade handshake. Do not rely on cookies alone because WebSocket does not automatically send cookies in all environments.

Is WebSocket encrypted by default? No. Use wss:// (WebSocket Secure) instead of ws:// to encrypt traffic over TLS. WSS uses the same certificate infrastructure as HTTPS and is required for production deployments.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro