Intermediate
35 min
Full Guide

Socket.io - Real-Time Library

Master Socket.io for building robust real-time applications with fallbacks

What is Socket.io?

Socket.io is the most popular real-time communication library for JavaScript. It provides a robust abstraction over WebSocket with automatic fallbacks, reconnection, rooms, namespaces, and many other features that make building real-time apps much easier.

While you could build everything with raw WebSockets, Socket.io handles the edge cases, connection management, and provides a cleaner API—especially for complex applications.

🔧 Socket.io Features

Connection

  • • Automatic reconnection with backoff
  • • Transport fallbacks (WebSocket → polling)
  • • Connection state management
  • • Multiplexing over single connection

Organization

  • • Namespaces for separation
  • • Rooms for grouping clients
  • • Broadcast to specific groups
  • • Private messaging

Reliability

  • • Acknowledgments (confirm receipt)
  • • Binary data support
  • • Packet buffering during disconnection
  • • Heartbeat mechanism

Developer Experience

  • • TypeScript support
  • • Event-based API
  • • Middleware support
  • • Debug mode

Basic Setup

// Install: npm install socket.io socket.io-client

// ============ SERVER (server.js) ============
const { Server } = require('socket.io');
const http = require('http');

const httpServer = http.createServer();
const io = new Server(httpServer, {
  cors: {
    origin: 'http://localhost:3000',
    methods: ['GET', 'POST']
  }
});

io.on('connection', (socket) => {
  console.log('Client connected:', socket.id);
  
  // Listen for events
  socket.on('chat message', (msg) => {
    console.log('Message:', msg);
    // Broadcast to all clients
    io.emit('chat message', msg);
  });
  
  socket.on('disconnect', (reason) => {
    console.log('Client disconnected:', reason);
  });
});

httpServer.listen(3001, () => {
  console.log('Socket.io server running on port 3001');
});

// ============ CLIENT (client.js) ============
import { io } from 'socket.io-client';

const socket = io('http://localhost:3001');

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

socket.on('chat message', (msg) => {
  console.log('Received:', msg);
});

// Send a message
socket.emit('chat message', 'Hello, everyone!');

Events and Acknowledgments

// Sending events with various data types
socket.emit('message', 'simple string');
socket.emit('user-data', { name: 'John', age: 30 });
socket.emit('numbers', 1, 2, 3);
socket.emit('mixed', 'text', { obj: true }, [1, 2, 3]);

// Acknowledgments - confirm message was received
// Client sends, waits for server confirmation
socket.emit('save-item', { id: 1, name: 'Test' }, (response) => {
  if (response.success) {
    console.log('Item saved!', response.id);
  } else {
    console.log('Failed:', response.error);
  }
});

// Server handles and responds
socket.on('save-item', (item, callback) => {
  try {
    const saved = database.save(item);
    callback({ success: true, id: saved.id });
  } catch (error) {
    callback({ success: false, error: error.message });
  }
});

// Timeout for acknowledgments (v4.4+)
socket.timeout(5000).emit('slow-operation', data, (err, response) => {
  if (err) {
    // Server didn't respond in 5 seconds
    console.log('Timeout!');
  } else {
    console.log('Response:', response);
  }
});

Rooms - Grouping Clients

// Rooms allow you to broadcast to subsets of clients

io.on('connection', (socket) => {
  // Join a room
  socket.on('join-room', (roomName) => {
    socket.join(roomName);
    console.log(`${socket.id} joined room: ${roomName}`);
    
    // Notify room members
    socket.to(roomName).emit('user-joined', {
      userId: socket.id,
      room: roomName
    });
  });
  
  // Leave a room
  socket.on('leave-room', (roomName) => {
    socket.leave(roomName);
    socket.to(roomName).emit('user-left', { userId: socket.id });
  });
  
  // Send message to specific room
  socket.on('room-message', ({ room, message }) => {
    // Send to everyone in room INCLUDING sender
    io.to(room).emit('message', {
      from: socket.id,
      text: message
    });
    
    // OR: Send to everyone EXCEPT sender
    socket.to(room).emit('message', {
      from: socket.id,
      text: message
    });
  });
  
  // Get rooms a socket is in
  console.log('Socket rooms:', socket.rooms); // Set { socket.id, 'room1', 'room2' }
  
  // Get all sockets in a room
  const socketsInRoom = await io.in('room1').fetchSockets();
  console.log('Users in room1:', socketsInRoom.length);
});

// Broadcast patterns
io.emit('to-everyone');                    // All connected clients
io.to('room1').emit('to-room1');           // Everyone in room1
io.to('room1').to('room2').emit('msg');    // Everyone in room1 OR room2
socket.to('room1').emit('except-sender');   // Room1 except sender
socket.broadcast.emit('except-sender');     // Everyone except sender

Namespaces - Separate Concerns

// Namespaces allow multiple apps over one connection

const io = new Server(httpServer);

// Main namespace (default: "/")
io.on('connection', (socket) => {
  console.log('Main namespace:', socket.id);
});

// Chat namespace
const chatNsp = io.of('/chat');
chatNsp.on('connection', (socket) => {
  console.log('Chat namespace:', socket.id);
  
  socket.on('message', (msg) => {
    chatNsp.emit('message', msg);
  });
});

// Admin namespace with auth
const adminNsp = io.of('/admin');
adminNsp.use((socket, next) => {
  const token = socket.handshake.auth.token;
  if (validateAdminToken(token)) {
    next();
  } else {
    next(new Error('Unauthorized'));
  }
});

adminNsp.on('connection', (socket) => {
  console.log('Admin connected:', socket.id);
  
  socket.on('broadcast', (message) => {
    // Broadcast to all namespaces
    io.emit('announcement', message);
  });
});

// Client connecting to namespaces
const chatSocket = io('http://localhost:3001/chat');
const adminSocket = io('http://localhost:3001/admin', {
  auth: { token: 'admin-secret-token' }
});

chatSocket.emit('message', 'Hello chat!');
adminSocket.emit('broadcast', 'Server announcement');

Middleware

// Server middleware - runs on every connection

// Authentication middleware
io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  
  if (!token) {
    return next(new Error('Authentication required'));
  }
  
  try {
    const user = verifyToken(token);
    socket.user = user; // Attach user to socket
    next();
  } catch (error) {
    next(new Error('Invalid token'));
  }
});

// Logging middleware
io.use((socket, next) => {
  console.log('New connection attempt from:', socket.handshake.address);
  next();
});

// Rate limiting middleware
const connectionCount = new Map();

io.use((socket, next) => {
  const ip = socket.handshake.address;
  const count = connectionCount.get(ip) || 0;
  
  if (count >= 10) {
    return next(new Error('Too many connections'));
  }
  
  connectionCount.set(ip, count + 1);
  socket.on('disconnect', () => {
    connectionCount.set(ip, connectionCount.get(ip) - 1);
  });
  
  next();
});

// Event middleware (per-socket)
socket.use(([event, ...args], next) => {
  console.log('Event received:', event, args);
  next(); // Continue to event handler
});

// Client-side error handling
socket.on('connect_error', (error) => {
  console.log('Connection error:', error.message);
  // 'Authentication required' or 'Invalid token'
});

Connection Options

// Client connection options
const socket = io('http://localhost:3001', {
  // Authentication
  auth: {
    token: 'user-jwt-token'
  },
  
  // Query parameters (visible in URL)
  query: {
    version: '1.0',
    platform: 'web'
  },
  
  // Transport options
  transports: ['websocket', 'polling'], // Try WebSocket first
  upgrade: true,            // Upgrade from polling to WebSocket
  
  // Reconnection
  reconnection: true,       // Enable auto-reconnect
  reconnectionAttempts: 5,  // Max attempts
  reconnectionDelay: 1000,  // Initial delay
  reconnectionDelayMax: 5000, // Max delay
  randomizationFactor: 0.5, // Randomize delay
  
  // Timeouts
  timeout: 20000,           // Connection timeout
  
  // Other
  autoConnect: true,        // Connect immediately
  forceNew: false,          // Reuse existing connection
  multiplex: true           // Multiple connections share socket
});

// Server options
const io = new Server(httpServer, {
  // CORS
  cors: {
    origin: ['http://localhost:3000', 'https://myapp.com'],
    methods: ['GET', 'POST'],
    credentials: true
  },
  
  // Transport
  transports: ['polling', 'websocket'],
  allowUpgrades: true,
  
  // Limits
  maxHttpBufferSize: 1e6,   // 1MB max message size
  pingTimeout: 20000,
  pingInterval: 25000,
  
  // Path
  path: '/socket.io/'       // Default path
});

Complete Chat Application

// Full-featured chat server

const { Server } = require('socket.io');
const http = require('http');

const server = http.createServer();
const io = new Server(server, {
  cors: { origin: '*' }
});

// Store user data
const users = new Map();

io.on('connection', (socket) => {
  console.log('Connected:', socket.id);
  
  // User joins with username
  socket.on('join', ({ username, room }) => {
    socket.username = username;
    socket.room = room;
    
    users.set(socket.id, { username, room });
    socket.join(room);
    
    // Welcome message to user
    socket.emit('message', {
      type: 'system',
      text: `Welcome to ${room}!`
    });
    
    // Announce to room
    socket.to(room).emit('message', {
      type: 'system',
      text: `${username} joined the room`
    });
    
    // Send room user list
    updateRoomUsers(room);
  });
  
  // Handle chat messages
  socket.on('message', (text) => {
    io.to(socket.room).emit('message', {
      type: 'chat',
      user: socket.username,
      text: text,
      timestamp: Date.now()
    });
  });
  
  // Typing indicator
  socket.on('typing', () => {
    socket.to(socket.room).emit('typing', {
      user: socket.username
    });
  });
  
  socket.on('stop-typing', () => {
    socket.to(socket.room).emit('stop-typing', {
      user: socket.username
    });
  });
  
  // Private message
  socket.on('private-message', ({ to, text }) => {
    io.to(to).emit('private-message', {
      from: socket.username,
      text: text
    });
  });
  
  // Disconnect
  socket.on('disconnect', () => {
    const user = users.get(socket.id);
    if (user) {
      io.to(user.room).emit('message', {
        type: 'system',
        text: `${user.username} left the room`
      });
      users.delete(socket.id);
      updateRoomUsers(user.room);
    }
  });
});

async function updateRoomUsers(room) {
  const sockets = await io.in(room).fetchSockets();
  const userList = sockets.map(s => ({
    id: s.id,
    username: s.username
  }));
  io.to(room).emit('room-users', userList);
}

server.listen(3001);

React Client Integration

// React hook for Socket.io
import { useEffect, useState, useCallback } from 'react';
import { io, Socket } from 'socket.io-client';

function useSocket(url: string) {
  const [socket, setSocket] = useState(null);
  const [connected, setConnected] = useState(false);
  
  useEffect(() => {
    const newSocket = io(url, {
      auth: { token: getAuthToken() }
    });
    
    newSocket.on('connect', () => setConnected(true));
    newSocket.on('disconnect', () => setConnected(false));
    
    setSocket(newSocket);
    
    return () => {
      newSocket.close();
    };
  }, [url]);
  
  return { socket, connected };
}

// Chat component
function Chat() {
  const { socket, connected } = useSocket('http://localhost:3001');
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState('');
  
  useEffect(() => {
    if (!socket) return;
    
    socket.on('message', (msg) => {
      setMessages(prev => [...prev, msg]);
    });
    
    socket.emit('join', { username: 'User', room: 'general' });
    
    return () => {
      socket.off('message');
    };
  }, [socket]);
  
  const sendMessage = useCallback(() => {
    if (socket && input.trim()) {
      socket.emit('message', input);
      setInput('');
    }
  }, [socket, input]);
  
  return (
    
{connected ? '🟢 Connected' : '🔴 Disconnected'}
{messages.map((msg, i) => (
{msg.user}: {msg.text}
))}
setInput(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && sendMessage()} />
); }

💡 Socket.io Best Practices

  • Use rooms for grouping related clients
  • Use namespaces to separate different parts of your app
  • Always use acknowledgments for important messages
  • Implement authentication via middleware
  • Handle disconnection gracefully
  • Use TypeScript for type-safe events
  • Enable debug mode in development: localStorage.debug = 'socket.io-client:*'