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).
| 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 |
npm install socket.io # Server
npm install socket.io-client # Client (or use CDN)
// 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'));
// 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!' });
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 });
});
});