Networking

WebSocket

A communication protocol providing full-duplex, bidirectional communication channels over a single TCP connection, enabling real-time data exchange between client and server with low latency.

What is WebSocket?

WebSocket is a computer communications protocol that provides full-duplex communication channels over a single TCP connection. Unlike HTTP’s request-response model, WebSocket enables bidirectional, real-time communication between client and server, making it ideal for applications requiring instant updates like chat, live feeds, and gaming.

WebSocket vs HTTP

Key Differences

interface ProtocolComparison {
  http: {
    pattern: 'Request-Response';
    connection: 'Short-lived';
    direction: 'Client → Server';
    overhead: 'High (headers with each request)';
    realtime: 'Poor (polling required)';
    useCases: ['REST APIs', 'Web pages', 'File downloads'];
  };
  webSocket: {
    pattern: 'Bidirectional streaming';
    connection: 'Long-lived, persistent';
    direction: 'Client ↔ Server';
    overhead: 'Low (initial handshake only)';
    realtime: 'Excellent (push-based)';
    useCases: ['Chat apps', 'Live feeds', 'Gaming', 'Collaboration tools'];
  };
}

// HTTP - Multiple requests
// Client  →  Request 1  →  Server
// Client  ←  Response 1 ←  Server
// Client  →  Request 2  →  Server
// Client  ←  Response 2 ←  Server

// WebSocket - Single connection
// Client  ↔  Handshake  ↔  Server
// Client  ↔  Message 1  ↔  Server
// Client  ↔  Message 2  ↔  Server
// Client  ↔  Message 3  ↔  Server

WebSocket Handshake

Connection Upgrade

// Client initiates upgrade from HTTP to WebSocket
const request = `
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://example.com
`;

// Server accepts upgrade
const response = `
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
`;

// After handshake, connection upgraded to WebSocket
// Now both can send/receive messages freely

Basic WebSocket Usage

Client-Side (Browser)

class WebSocketClient {
  private ws: WebSocket | null = null;
  private reconnectAttempts: number = 0;
  private maxReconnectAttempts: number = 5;

  connect(url: string) {
    this.ws = new WebSocket(url);

    this.ws.onopen = (event) => {
      console.log('WebSocket connected');
      this.reconnectAttempts = 0;

      // Send initial message
      this.send({ type: 'auth', token: 'user-token' });
    };

    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      console.log('Received:', data);

      this.handleMessage(data);
    };

    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };

    this.ws.onclose = (event) => {
      console.log('WebSocket closed:', event.code, event.reason);

      // Attempt reconnection
      if (this.reconnectAttempts < this.maxReconnectAttempts) {
        this.reconnectAttempts++;
        const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);

        console.log(`Reconnecting in ${delay}ms...`);
        setTimeout(() => this.connect(url), delay);
      }
    };
  }

  send(data: any) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data));
    } else {
      console.error('WebSocket not connected');
    }
  }

  private handleMessage(data: any) {
    switch (data.type) {
      case 'message':
        console.log('Chat message:', data.content);
        break;
      case 'notification':
        console.log('Notification:', data.message);
        break;
      case 'update':
        console.log('Update:', data.payload);
        break;
    }
  }

  disconnect() {
    if (this.ws) {
      this.ws.close(1000, 'Client disconnecting');
      this.ws = null;
    }
  }

  getState(): string {
    if (!this.ws) return 'DISCONNECTED';

    const states = {
      [WebSocket.CONNECTING]: 'CONNECTING',
      [WebSocket.OPEN]: 'OPEN',
      [WebSocket.CLOSING]: 'CLOSING',
      [WebSocket.CLOSED]: 'CLOSED'
    };

    return states[this.ws.readyState];
  }
}

// Usage
const client = new WebSocketClient();
client.connect('wss://api.example.com/ws');

// Send messages
client.send({ type: 'message', content: 'Hello!' });

// Disconnect
client.disconnect();

Server-Side (Node.js with ws)

import { WebSocketServer, WebSocket } from 'ws';
import http from 'http';

class WSServer {
  private wss: WebSocketServer;
  private clients: Map<string, WebSocket> = new Map();

  constructor(port: number) {
    const server = http.createServer();

    this.wss = new WebSocketServer({ server });

    this.wss.on('connection', (ws, req) => {
      const clientId = this.generateClientId();
      this.clients.set(clientId, ws);

      console.log(`Client connected: ${clientId}`);
      console.log(`Total clients: ${this.clients.size}`);

      ws.on('message', (data) => {
        this.handleMessage(clientId, data.toString());
      });

      ws.on('close', () => {
        console.log(`Client disconnected: ${clientId}`);
        this.clients.delete(clientId);
      });

      ws.on('error', (error) => {
        console.error(`Client error ${clientId}:`, error);
      });

      // Send welcome message
      ws.send(JSON.stringify({
        type: 'welcome',
        clientId,
        message: 'Connected to server'
      }));
    });

    server.listen(port, () => {
      console.log(`WebSocket server listening on port ${port}`);
    });
  }

  private handleMessage(clientId: string, message: string) {
    try {
      const data = JSON.parse(message);

      console.log(`Message from ${clientId}:`, data);

      switch (data.type) {
        case 'message':
          this.broadcast(data, clientId);
          break;
        case 'private':
          this.sendToClient(data.targetId, data);
          break;
        default:
          console.log('Unknown message type:', data.type);
      }
    } catch (error) {
      console.error('Invalid message format:', error);
    }
  }

  private broadcast(data: any, exclude?: string) {
    const message = JSON.stringify(data);

    this.clients.forEach((ws, clientId) => {
      if (clientId !== exclude && ws.readyState === WebSocket.OPEN) {
        ws.send(message);
      }
    });
  }

  private sendToClient(clientId: string, data: any) {
    const ws = this.clients.get(clientId);

    if (ws && ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify(data));
    }
  }

  private generateClientId(): string {
    return `client-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }

  getClientsCount(): number {
    return this.clients.size;
  }
}

// Usage
const server = new WSServer(8080);

Real-World Applications

Chat Application

class ChatApp {
  private ws: WebSocket;
  private username: string;

  constructor(url: string, username: string) {
    this.username = username;
    this.ws = new WebSocket(url);

    this.ws.onopen = () => {
      // Join chat room
      this.send({
        type: 'join',
        username: this.username,
        room: 'general'
      });
    };

    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);

      switch (data.type) {
        case 'message':
          this.displayMessage(data.username, data.content);
          break;
        case 'userJoined':
          this.displaySystemMessage(`${data.username} joined the chat`);
          break;
        case 'userLeft':
          this.displaySystemMessage(`${data.username} left the chat`);
          break;
      }
    };
  }

  sendMessage(content: string) {
    this.send({
      type: 'message',
      content,
      timestamp: Date.now()
    });
  }

  private send(data: any) {
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data));
    }
  }

  private displayMessage(username: string, content: string) {
    console.log(`${username}: ${content}`);
    // Update UI
  }

  private displaySystemMessage(message: string) {
    console.log(`[System] ${message}`);
    // Update UI
  }
}

// Usage
const chat = new ChatApp('wss://chat.example.com', 'Alice');
chat.sendMessage('Hello everyone!');

Live Dashboard

class LiveDashboard {
  private ws: WebSocket;
  private metrics: Map<string, any> = new Map();

  constructor(url: string) {
    this.ws = new WebSocket(url);

    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);

      if (data.type === 'metrics') {
        this.updateMetrics(data.metrics);
      }
    };
  }

  private updateMetrics(metrics: Record<string, any>) {
    Object.entries(metrics).forEach(([key, value]) => {
      const previous = this.metrics.get(key);
      this.metrics.set(key, value);

      // Calculate change
      if (previous !== undefined) {
        const change = value - previous;
        const percentage = (change / previous) * 100;

        console.log(`${key}: ${value} (${percentage.toFixed(2)}%)`);
      }

      // Update UI charts/graphs
      this.updateChart(key, value);
    });
  }

  private updateChart(metric: string, value: any) {
    // Update dashboard UI
    console.log(`Update chart: ${metric} = ${value}`);
  }

  subscribe(metrics: string[]) {
    this.ws.send(JSON.stringify({
      type: 'subscribe',
      metrics
    }));
  }
}

// Usage
const dashboard = new LiveDashboard('wss://dashboard.example.com');
dashboard.subscribe(['cpu', 'memory', 'requests']);

WebSocket with Proxies

Proxying WebSocket Connections

# Nginx reverse proxy for WebSocket
location /ws/ {
    proxy_pass http://backend:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # WebSocket timeout settings
    proxy_read_timeout 86400s;
    proxy_send_timeout 86400s;
}

Node.js WebSocket Proxy

import { WebSocketServer, WebSocket } from 'ws';
import http from 'http';

class WebSocketProxy {
  constructor(listenPort: number, targetUrl: string) {
    const server = http.createServer();
    const wss = new WebSocketServer({ server });

    wss.on('connection', (clientWs) => {
      console.log('Client connected to proxy');

      // Connect to target server
      const targetWs = new WebSocket(targetUrl);

      targetWs.on('open', () => {
        console.log('Connected to target server');

        // Pipe messages between client and target
        clientWs.on('message', (data) => {
          if (targetWs.readyState === WebSocket.OPEN) {
            targetWs.send(data);
          }
        });

        targetWs.on('message', (data) => {
          if (clientWs.readyState === WebSocket.OPEN) {
            clientWs.send(data);
          }
        });
      });

      targetWs.on('close', () => {
        clientWs.close();
      });

      clientWs.on('close', () => {
        targetWs.close();
      });
    });

    server.listen(listenPort, () => {
      console.log(`WebSocket proxy listening on port ${listenPort}`);
    });
  }
}

// Usage - Proxy WebSocket traffic
new WebSocketProxy(8080, 'wss://target-server.com/ws');

Security

Secure WebSocket (WSS)

// Always use wss:// in production
const secureWs = new WebSocket('wss://api.example.com/ws');

// vs insecure ws://
const insecureWs = new WebSocket('ws://api.example.com/ws'); // Don't use

// WSS provides:
// - Encrypted communication (TLS/SSL)
// - Authentication
// - Data integrity
// - Protection from man-in-the-middle attacks

Authentication

class AuthenticatedWebSocket {
  private ws: WebSocket | null = null;
  private token: string;

  constructor(url: string, token: string) {
    this.token = token;

    // Method 1: Token in URL query parameter
    this.ws = new WebSocket(`${url}?token=${token}`);

    // Method 2: Token in first message
    this.ws.onopen = () => {
      this.ws?.send(JSON.stringify({
        type: 'auth',
        token: this.token
      }));
    };

    // Method 3: Token in subprotocol header
    this.ws = new WebSocket(url, [`auth.${token}`]);
  }
}

// Server-side validation
class SecureWSServer {
  private wss: WebSocketServer;

  constructor(port: number) {
    this.wss = new WebSocketServer({
      port,
      verifyClient: (info, callback) => {
        // Verify token
        const token = new URL(info.req.url!, 'ws://base').searchParams.get('token');

        if (this.isValidToken(token)) {
          callback(true);
        } else {
          callback(false, 401, 'Unauthorized');
        }
      }
    });
  }

  private isValidToken(token: string | null): boolean {
    // Validate JWT or session token
    return token !== null && token.length > 0;
  }
}

Rate Limiting

class RateLimitedWSServer {
  private clients: Map<string, {
    ws: WebSocket;
    messageCount: number;
    lastReset: number;
  }> = new Map();

  private readonly MAX_MESSAGES_PER_MINUTE = 60;

  handleMessage(clientId: string, message: string) {
    const client = this.clients.get(clientId);
    if (!client) return;

    const now = Date.now();

    // Reset counter every minute
    if (now - client.lastReset > 60000) {
      client.messageCount = 0;
      client.lastReset = now;
    }

    // Check rate limit
    if (client.messageCount >= this.MAX_MESSAGES_PER_MINUTE) {
      client.ws.send(JSON.stringify({
        type: 'error',
        message: 'Rate limit exceeded'
      }));
      return;
    }

    client.messageCount++;

    // Process message
    this.processMessage(message);
  }

  private processMessage(message: string) {
    // Handle message
  }
}

Heartbeat / Ping-Pong

Keep-Alive Implementation

class WebSocketWithHeartbeat {
  private ws: WebSocket;
  private pingInterval: NodeJS.Timeout | null = null;
  private pongTimeout: NodeJS.Timeout | null = null;

  constructor(url: string) {
    this.ws = new WebSocket(url);

    this.ws.onopen = () => {
      this.startHeartbeat();
    };

    this.ws.onclose = () => {
      this.stopHeartbeat();
    };

    // Listen for pong
    this.ws.addEventListener('message', (event) => {
      const data = JSON.parse(event.data);

      if (data.type === 'pong') {
        this.receivedPong();
      }
    });
  }

  private startHeartbeat() {
    // Send ping every 30 seconds
    this.pingInterval = setInterval(() => {
      this.sendPing();
    }, 30000);
  }

  private stopHeartbeat() {
    if (this.pingInterval) {
      clearInterval(this.pingInterval);
    }
    if (this.pongTimeout) {
      clearTimeout(this.pongTimeout);
    }
  }

  private sendPing() {
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({ type: 'ping' }));

      // Expect pong within 5 seconds
      this.pongTimeout = setTimeout(() => {
        console.log('No pong received - connection lost');
        this.ws.close();
      }, 5000);
    }
  }

  private receivedPong() {
    if (this.pongTimeout) {
      clearTimeout(this.pongTimeout);
    }
  }
}

Best Practices

For Clients

  1. Always use WSS (secure WebSocket) in production
  2. Implement reconnection logic with exponential backoff
  3. Handle connection state changes properly
  4. Validate all incoming messages
  5. Implement heartbeat/ping-pong

For Servers

  1. Authenticate connections
  2. Implement rate limiting
  3. Set appropriate timeouts
  4. Handle client disconnections gracefully
  5. Use message validation

For Performance

  1. Minimize message size (use binary when possible)
  2. Batch multiple small messages
  3. Implement message compression
  4. Use connection pooling on server
  5. Monitor active connections

Learn More

Create a free Account to use WebSocket in Production

Say goodbye to CORS errors and get back to building great web applications. It's free!

CORSPROXY Dashboard

Related Terms

More in Networking

Related guides

Back to Glossary