Skip to main content

Real-time APIs with WebSockets: Implementation Guide

Created: February 23, 2026 Larry Qu 4 min read

Introduction

Real-time communication enables instant updates, live collaboration, and interactive experiences. This guide covers WebSocket implementation patterns, from native APIs to managed solutions.


Real-Time Options Comparison

┌─────────────────────────────────────────────────────────────┐
│              Real-Time Technologies                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  WebSocket                                                 │
│  • Full-duplex communication                               │
│  • Bidirectional messages                                  │
│  • Native browser support                                  │
│                                                             │
│  Socket.io                                                  │
│  • Abstraction over WebSocket                             │
│  • Fallbacks, rooms, auto-reconnect                       │
│                                                             │
│  Server-Sent Events (SSE)                                 │
│  • One-way (server to client)                             │
│  • Simple, HTTP-based                                     │
│  • Great for notifications                                │
│                                                             │
│  Managed Services                                          │
│  • Pusher, Ably, Supabase Realtime                       │
│  • Don't manage infrastructure                            │
│  • Scale automatically                                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Native WebSockets

Server Implementation

// Node.js WebSocket server
import { WebSocketServer, WebSocket } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

interface Client {
  ws: WebSocket;
  userId?: string;
}

const clients = new Map<WebSocket, Client>();

wss.on('connection', (ws, req) => {
  // Store client
  clients.set(ws, { ws });
  
  ws.on('message', (message) => {
    const data = JSON.parse(message.toString());
    
    switch (data.type) {
      case 'auth':
        // Authenticate user
        const client = clients.get(ws);
        if (client) client.userId = data.userId;
        break;
        
      case 'message':
        // Broadcast to room
        broadcastToRoom(data.room, {
          type: 'message',
          content: data.content,
          from: clients.get(ws)?.userId,
        });
        break;
        
      case 'join':
        // Join room
        ws.send(JSON.stringify({ type: 'joined', room: data.room }));
        break;
    }
  });
  
  ws.on('close', () => {
    clients.delete(ws);
  });
});

function broadcastToRoom(room: string, message: object) {
  const msg = JSON.stringify(message);
  clients.forEach((client) => {
    client.ws.send(msg);
  });
}

Client Implementation

// Client WebSocket
class RealTimeClient {
  private ws: WebSocket;
  private handlers = new Map<string, (data: any) => void>();
  
  constructor(url: string) {
    this.ws = new WebSocket(url);
    
    this.ws.onopen = () => {
      console.log('Connected');
    };
    
    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      const handler = this.handlers.get(data.type);
      if (handler) handler(data);
    };
  }
  
  on(type: string, handler: (data: any) => void) {
    this.handlers.set(type, handler);
  }
  
  send(type: string, data: any) {
    this.ws.send(JSON.stringify({ type, ...data }));
  }
}

// Usage
const client = new RealTimeClient('ws://localhost:8080');

client.on('message', (data) => {
  console.log('New message:', data.content);
});

client.send('auth', { token: 'user-token' });

Socket.io

Server Setup

// Socket.io server
import { Server } from 'socket.io';
import { createServer } from 'http';

const httpServer = createServer(app);
const io = new Server(httpServer, {
  cors: {
    origin: '*',
  },
});

io.on('connection', (socket) => {
  console.log('Client connected:', socket.id);
  
  // Join room
  socket.on('join-room', (roomId: string) => {
    socket.join(roomId);
    socket.to(roomId).emit('user-joined', socket.id);
  });
  
  // Leave room
  socket.on('leave-room', (roomId: string) => {
    socket.leave(roomId);
  });
  
  // Send message to room
  socket.on('chat-message', (data: { room: string; message: string }) => {
    io.to(data.room).emit('chat-message', {
      message: data.message,
      sender: socket.id,
      timestamp: Date.now(),
    });
  });
  
  // Private message
  socket.on('private-message', (data: { to: string; message: string }) => {
    io.to(data.to).emit('private-message', {
      message: data.message,
      from: socket.id,
    });
  });
  
  socket.on('disconnect', () => {
    console.log('Client disconnected:', socket.id);
  });
});

httpServer.listen(3000);

Client Usage

// Socket.io client
import { io, Socket } from 'socket.io-client';

const socket: Socket = io('http://localhost:3000', {
  auth: { token: 'user-token' },
  transports: ['websocket'], // Force WebSocket
});

socket.on('connect', () => {
  console.log('Connected:', socket.id);
});

// Join room
socket.emit('join-room', 'room-123');

// Listen for messages
socket.on('chat-message', (data) => {
  console.log('New message:', data.message);
});

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

Server-Sent Events (SSE)

Simple Implementation

// Express SSE endpoint
app.get('/api/events', (req, res) => {
  // Set headers for SSE
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  res.flushHeaders();
  
  // Send initial message
  res.write('data: {"status": "connected"}\n\n');
  
  // Send updates periodically
  const interval = setInterval(() => {
    const data = JSON.stringify({ 
      time: new Date().toISOString(),
      users: getOnlineUsers() 
    });
    res.write(`data: ${data}\n\n`);
  }, 5000);
  
  // Clean up on close
  req.on('close', () => {
    clearInterval(interval);
    res.end();
  });
});

Client Implementation

// SSE client
const eventSource = new EventSource('/api/events');

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

eventSource.onerror = (error) => {
  console.error('SSE error:', error);
  eventSource.close();
};

// Clean up
// eventSource.close();

Scaling WebSockets

Redis Adapter

// Scale with Redis (Socket.io)
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';

const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate();

const io = new Server({
  adapter: createAdapter(pubClient, subClient),
});

// Now handles multiple server instances

Connection Management

# Production considerations
production:
  load_balancing:
    - "Sticky sessions required"
    - "Redis for state"
    
  monitoring:
    - "Track connected clients"
    - "Alert on disconnections"
    - "Measure message throughput"
    
  security:
    - "WSS (WebSocket Secure)"
    - "Authentication on connect"
    - "Rate limiting"

Managed Solutions

Supabase Realtime

// Supabase Realtime
import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_KEY!
);

// Subscribe to database changes
const channel = supabase
  .channel('posts')
  .on('postgres_changes', {
    event: '*',
    schema: 'public',
    table: 'posts',
  }, (payload) => {
    console.log('Change received!', payload);
  })
  .subscribe();

// Unsubscribe
channel.unsubscribe();

Key Takeaways

  • Native WebSocket - Full control, no dependencies
  • Socket.io - Best DX, great features, automatic fallbacks
  • SSE - Simpler for server-to-client only
  • Managed services - Don’t manage infrastructure

External Resources

Resources

Comments

Share this article

Scan to read on mobile