Socket.io is a library built on top of WebSocket that bundles rooms, namespaces, auto-reconnect, broadcast helpers, and more — all out of the box. It is not WebSocket itself, but uses it under the hood (with HTTP polling fallback when WebSocket is unavailable).


Raw ws vs Socket.io — Feature Matrix

Feature Raw WebSocket Socket.io
Auto-reconnect ❌ Build yourself ✅ Built-in with backoff
Rooms ❌ Map/Set boilerplate socket.join()
Namespaces ❌ Not available io.of('/chat')
Broadcast helpers ❌ Manual forEach loop socket.broadcast.emit()
Acknowledgements ❌ Build yourself ✅ Callback pattern
Fallback transport ❌ WS or nothing ✅ Falls back to HTTP polling
Middleware support ❌ Manual per connection io.use((socket, next) => {})
Binary data ✅ Native ✅ Supported
Payload overhead ✅ Minimal (raw frames) ⚠️ Slightly larger

Installation

npm install socket.io          # Server
npm install socket.io-client   # Client (or use CDN)

Basic Server

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

const app    = express();
const server = http.createServer(app);
const io     = new Server(server, {
  cors: {
    origin: '<http://localhost:5173>', // Vite dev server
    methods: ['GET', 'POST'],
  },
});

io.on('connection', (socket) => {
  console.log('User connected:', socket.id);

  socket.emit('welcome', { message: 'Hello! 🎉', id: socket.id });

  socket.on('message', (data) => {
    console.log('Received:', data);
    // Echo back
    socket.emit('message', { ...data, echo: true });
  });

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

server.listen(3000, () => console.log('Server on port 3000'));

Basic Client

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

const socket = io('<http://localhost:3000>');

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

socket.on('welcome', (data) => {
  console.log(data.message);
});

socket.on('message', (data) => {
  console.log('Message:', data);
});

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

// Send a message
socket.emit('message', { text: 'Hello everyone!' });

Rooms — The Socket.io Way

Compare Socket.io's built-in rooms vs the raw WebSocket approach:

Raw WebSocket (∼30 lines of boilerplate):

const rooms = new Map();

function joinRoom(ws, room) {
  if (!rooms.has(room)) rooms.set(room, new Set());
  rooms.get(room).add(ws);
  ws.currentRooms = ws.currentRooms || new Set();
  ws.currentRooms.add(room);
}

function toRoom(room, data, exc) {
  rooms.get(room)?.forEach(c => {
    if (c !== exc && c.readyState === WS.OPEN)
      c.send(JSON.stringify(data));
  });
}

wss.on('connection', (ws) => {
  ws.on('message', (raw) => {
    const { type, room, text } = JSON.parse(raw);
    if (type === 'join')    joinRoom(ws, room);
    if (type === 'message') toRoom(room, { text }, ws);
  });
  ws.on('close', () => ws.currentRooms?.forEach(r => leaveRoom(ws, r)));
});

**Socket.io (8 lines, same result):**

io.on('connection', (socket) => {

  socket.on('join', (room) => {
    socket.join(room); // done ✅
  });

  socket.on('message', ({ room, text }) => {
    // broadcast to room, excluding sender
    socket.to(room).emit('message', { text });
  });

});