Intermediate
25 min
Full Guide

WebSockets & Real-Time APIs

Build real-time applications with WebSockets, Server-Sent Events, and Socket.io

What are WebSockets?

WebSockets provide full-duplex, bidirectional communication between client and server over a single, persistent connection. Unlike HTTP where the client always initiates requests, WebSockets allow both client and server to send messages at any time.

This makes WebSockets perfect for real-time applications like chat apps, live notifications, gaming, collaborative editing, and live data feeds.

🔄 HTTP vs WebSocket

HTTP (Request/Response)

  • • Client initiates every request
  • • Connection closes after response
  • • Server can't push data
  • • Polling needed for updates

WebSocket (Bidirectional)

  • • Both can send anytime
  • • Persistent connection
  • • Server pushes instantly
  • • True real-time updates

Basic WebSocket Connection

// Creating a WebSocket connection
const socket = new WebSocket('wss://api.example.com/ws');

// Connection opened
socket.onopen = function(event) {
  console.log('Connected to WebSocket server');
  
  // Send a message
  socket.send(JSON.stringify({
    type: 'subscribe',
    channel: 'notifications'
  }));
};

// Receiving messages
socket.onmessage = function(event) {
  const data = JSON.parse(event.data);
  console.log('Received:', data);
  
  // Handle different message types
  switch (data.type) {
    case 'notification':
      showNotification(data.payload);
      break;
    case 'chat_message':
      addMessage(data.payload);
      break;
    case 'user_status':
      updateUserStatus(data.payload);
      break;
  }
};

// Connection closed
socket.onclose = function(event) {
  if (event.wasClean) {
    console.log(`Connection closed cleanly, code=${event.code}`);
  } else {
    console.log('Connection lost');
  }
};

// Error handling
socket.onerror = function(error) {
  console.error('WebSocket error:', error);
};

// Sending messages
function sendMessage(type, payload) {
  if (socket.readyState === WebSocket.OPEN) {
    socket.send(JSON.stringify({ type, payload }));
  } else {
    console.warn('WebSocket not connected');
  }
}

// Close connection
socket.close(1000, 'User logged out');

WebSocket Connection States

🔄

CONNECTING

0

OPEN

1

CLOSING

2

CLOSED

3

// Checking connection state
if (socket.readyState === WebSocket.OPEN) {
  socket.send(message);
}

// State constants
WebSocket.CONNECTING // 0 - Connection in progress
WebSocket.OPEN       // 1 - Connection established
WebSocket.CLOSING    // 2 - Closing handshake in progress
WebSocket.CLOSED     // 3 - Connection closed

Reconnection with Exponential Backoff

// Robust WebSocket with auto-reconnect
class ReconnectingWebSocket {
  constructor(url, options = {}) {
    this.url = url;
    this.options = {
      maxRetries: 10,
      baseDelay: 1000,
      maxDelay: 30000,
      ...options
    };
    
    this.retryCount = 0;
    this.handlers = new Map();
    this.messageQueue = [];
    
    this.connect();
  }

  connect() {
    this.socket = new WebSocket(this.url);
    
    this.socket.onopen = () => {
      console.log('WebSocket connected');
      this.retryCount = 0;
      
      // Send queued messages
      while (this.messageQueue.length > 0) {
        const message = this.messageQueue.shift();
        this.socket.send(message);
      }
      
      this.emit('open');
    };

    this.socket.onmessage = (event) => {
      const data = JSON.parse(event.data);
      this.emit('message', data);
      
      // Emit typed events
      if (data.type) {
        this.emit(data.type, data.payload);
      }
    };

    this.socket.onclose = (event) => {
      this.emit('close', event);
      
      if (!event.wasClean) {
        this.reconnect();
      }
    };

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

  reconnect() {
    if (this.retryCount >= this.options.maxRetries) {
      console.error('Max reconnection attempts reached');
      this.emit('max_retries');
      return;
    }

    // Exponential backoff with jitter
    const delay = Math.min(
      this.options.baseDelay * Math.pow(2, this.retryCount) + 
      Math.random() * 1000,
      this.options.maxDelay
    );

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

  send(type, payload) {
    const message = JSON.stringify({ type, payload });
    
    if (this.socket.readyState === WebSocket.OPEN) {
      this.socket.send(message);
    } else {
      // Queue message for when connection opens
      this.messageQueue.push(message);
    }
  }

  on(event, handler) {
    if (!this.handlers.has(event)) {
      this.handlers.set(event, []);
    }
    this.handlers.get(event).push(handler);
  }

  emit(event, data) {
    const handlers = this.handlers.get(event) || [];
    handlers.forEach(handler => handler(data));
  }

  close() {
    this.socket.close(1000, 'User initiated close');
  }
}

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

ws.on('open', () => console.log('Connected!'));
ws.on('notification', (data) => showNotification(data));
ws.on('chat_message', (data) => addMessage(data));

ws.send('subscribe', { channel: 'updates' });

Chat Application Example

// Real-time chat with WebSockets
class ChatClient {
  constructor(serverUrl, userId) {
    this.userId = userId;
    this.ws = new ReconnectingWebSocket(serverUrl);
    this.messageCallbacks = [];
    
    this.ws.on('open', () => {
      // Authenticate on connect
      this.ws.send('auth', { userId: this.userId });
    });
    
    this.ws.on('chat_message', (message) => {
      this.messageCallbacks.forEach(cb => cb(message));
    });
    
    this.ws.on('user_joined', (user) => {
      console.log(`${user.name} joined the chat`);
    });
    
    this.ws.on('user_left', (user) => {
      console.log(`${user.name} left the chat`);
    });
    
    this.ws.on('typing', ({ userId, isTyping }) => {
      this.updateTypingIndicator(userId, isTyping);
    });
  }

  joinRoom(roomId) {
    this.currentRoom = roomId;
    this.ws.send('join_room', { roomId });
  }

  leaveRoom() {
    if (this.currentRoom) {
      this.ws.send('leave_room', { roomId: this.currentRoom });
      this.currentRoom = null;
    }
  }

  sendMessage(text) {
    this.ws.send('chat_message', {
      roomId: this.currentRoom,
      text,
      timestamp: Date.now()
    });
  }

  startTyping() {
    this.ws.send('typing', { roomId: this.currentRoom, isTyping: true });
  }

  stopTyping() {
    this.ws.send('typing', { roomId: this.currentRoom, isTyping: false });
  }

  onMessage(callback) {
    this.messageCallbacks.push(callback);
  }

  updateTypingIndicator(userId, isTyping) {
    // Update UI to show "User is typing..."
  }
}

// Usage
const chat = new ChatClient('wss://chat.example.com', 'user-123');

chat.joinRoom('room-456');
chat.onMessage((message) => {
  addMessageToUI(message);
});

// Send message
sendButton.onclick = () => {
  chat.sendMessage(input.value);
  input.value = '';
};

// Typing indicator
let typingTimeout;
input.oninput = () => {
  chat.startTyping();
  clearTimeout(typingTimeout);
  typingTimeout = setTimeout(() => chat.stopTyping(), 2000);
};

Server-Sent Events (SSE)

For one-way server-to-client streaming, SSE is simpler than WebSockets:

// Server-Sent Events - simpler alternative for one-way updates
const eventSource = new EventSource('https://api.example.com/events');

// Default message event
eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Received:', data);
};

// Named events
eventSource.addEventListener('notification', (event) => {
  const notification = JSON.parse(event.data);
  showNotification(notification);
});

eventSource.addEventListener('price_update', (event) => {
  const price = JSON.parse(event.data);
  updatePriceDisplay(price);
});

// Connection events
eventSource.onopen = () => {
  console.log('SSE connection opened');
};

eventSource.onerror = (error) => {
  if (eventSource.readyState === EventSource.CLOSED) {
    console.log('SSE connection closed');
  } else {
    console.error('SSE error:', error);
  }
};

// Close connection
eventSource.close();

// SSE with authentication (using fetch for custom headers)
async function createAuthenticatedSSE(url, token) {
  const response = await fetch(url, {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
  
  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  
  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    
    const text = decoder.decode(value);
    const lines = text.split('\n');
    
    for (const line of lines) {
      if (line.startsWith('data: ')) {
        const data = JSON.parse(line.slice(6));
        handleEvent(data);
      }
    }
  }
}

Socket.io - Popular WebSocket Library

// Socket.io provides additional features:
// - Auto-reconnection
// - Room/namespace support  
// - Fallback to HTTP polling
// - Message acknowledgments

import { io } from 'socket.io-client';

// Connect with options
const socket = io('https://api.example.com', {
  auth: {
    token: 'your-jwt-token'
  },
  reconnection: true,
  reconnectionAttempts: 5,
  reconnectionDelay: 1000
});

// Connection events
socket.on('connect', () => {
  console.log('Connected with ID:', socket.id);
});

socket.on('disconnect', (reason) => {
  console.log('Disconnected:', reason);
});

socket.on('connect_error', (error) => {
  console.error('Connection error:', error);
});

// Sending and receiving messages
socket.emit('chat_message', {
  room: 'general',
  text: 'Hello everyone!'
});

socket.on('chat_message', (message) => {
  addMessageToUI(message);
});

// With acknowledgment (like a callback)
socket.emit('send_message', { text: 'Hello' }, (response) => {
  console.log('Message sent, server responded:', response);
});

// Rooms (join/leave)
socket.emit('join_room', 'room-123');
socket.emit('leave_room', 'room-123');

// Namespaces
const adminSocket = io('https://api.example.com/admin');
const chatSocket = io('https://api.example.com/chat');

// Volatile messages (can be dropped if not ready)
socket.volatile.emit('cursor_position', { x: 100, y: 200 });

// Binary data
socket.emit('file_upload', fileBuffer);

Real-Time Data Visualization

// Live stock price updates
class StockTicker {
  constructor() {
    this.ws = new ReconnectingWebSocket('wss://stocks.example.com');
    this.subscriptions = new Set();
    this.priceCallbacks = new Map();
    
    this.ws.on('price_update', (data) => {
      const callbacks = this.priceCallbacks.get(data.symbol) || [];
      callbacks.forEach(cb => cb(data));
    });
  }

  subscribe(symbol, callback) {
    if (!this.subscriptions.has(symbol)) {
      this.subscriptions.add(symbol);
      this.ws.send('subscribe', { symbol });
    }
    
    if (!this.priceCallbacks.has(symbol)) {
      this.priceCallbacks.set(symbol, []);
    }
    this.priceCallbacks.get(symbol).push(callback);
  }

  unsubscribe(symbol) {
    this.subscriptions.delete(symbol);
    this.priceCallbacks.delete(symbol);
    this.ws.send('unsubscribe', { symbol });
  }
}

// Usage
const ticker = new StockTicker();

ticker.subscribe('AAPL', (data) => {
  document.getElementById('aapl-price').textContent = data.price;
  document.getElementById('aapl-change').textContent = data.change;
});

ticker.subscribe('GOOGL', (data) => {
  updateChart('GOOGL', data);

  
});

💡 WebSocket Best Practices

  • Implement reconnection logic - Connections will drop
  • Use exponential backoff - Don't hammer the server
  • Send heartbeats/pings - Keep connection alive
  • Queue messages when disconnected - Send when reconnected
  • Use JSON for messages - Structured, parseable data
  • Handle all connection states - Open, close, error events