Getting started
Server setup (Node.js)
// ES6 import
import { Server } from "socket.io";
// CommonJS
const { Server } = require("socket.io");
// Standalone server
const io = new Server(3000);
// With Express
const app = require("express")();
const server = require("http").createServer(app);
const io = new Server(server);
server.listen(3000);
Client setup (Browser)
<!-- CDN -->
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
</script>
// ES6 module
import { io } from "socket.io-client";
const socket = io("http://localhost:3000");
// CommonJS
const io = require("socket.io-client");
const socket = io("http://localhost:3000");
Basic connection
// Server
io.on("connection", (socket) => {
console.log("User connected:", socket.id);
socket.on("disconnect", () => {
console.log("User disconnected");
});
});
// Client
const socket = io();
socket.on("connect", () => {
console.log("Connected:", socket.id);
});
Emitting events
Server to client
// To single client
socket.emit("message", "Hello client");
// To all clients
io.emit("broadcast", "Hello everyone");
// To all except sender
socket.broadcast.emit("news", "New user joined");
// To specific room
io.to("room1").emit("roomMsg", "Hello room");
// To multiple rooms
io.to("room1").to("room2").emit("multi", "data");
// With acknowledgement callback
socket.emit("question", "data", (answer) => {
console.log(answer);
});
Client to server
// Basic emit
socket.emit("message", "Hello server");
// With multiple arguments
socket.emit("chat", "username", "message text");
// With object
socket.emit("user", { name: "John", age: 30 });
// With callback (acknowledgement)
socket.emit("question", "data", (response) => {
console.log("Server response:", response);
});
Listening to events
Server-side listeners
io.on("connection", (socket) => {
// Single event
socket.on("message", (data) => {
console.log("Received:", data);
});
// With acknowledgement
socket.on("question", (data, callback) => {
console.log(data);
callback("Answer from server");
});
// Multiple arguments
socket.on("chat", (user, msg, time) => {
console.log(`${user} at ${time}: ${msg}`);
});
// Catch-all listener
socket.onAny((eventName, ...args) => {
console.log(`Event: ${eventName}`, args);
});
});
Client-side listeners
// Basic listener
socket.on("message", (data) => {
console.log("Received:", data);
});
// With acknowledgement
socket.on("question", (data, callback) => {
console.log(data);
callback("Answer from client");
});
// Connection events
socket.on("connect", () => {
console.log("Connected");
});
socket.on("disconnect", () => {
console.log("Disconnected");
});
socket.on("connect_error", (error) => {
console.error("Connection error:", error);
});
Rooms
Joining and leaving
// Join room
socket.join("room1");
// Join multiple rooms
socket.join(["room1", "room2", "room3"]);
// Leave room
socket.leave("room1");
// Join room conditionally
io.on("connection", (socket) => {
socket.on("joinRoom", (room) => {
socket.join(room);
socket.emit("joined", `You joined ${room}`);
});
});
Broadcasting to rooms
// To specific room
io.to("room1").emit("message", "Hello room1");
// To multiple rooms
io.to("room1").to("room2").emit("news", "data");
// To room except sender
socket.to("room1").emit("message", "Others in room");
// Get all sockets in room
const sockets = await io.in("room1").fetchSockets();
console.log("Room size:", sockets.length);
// Get all rooms
const rooms = Array.from(socket.rooms);
console.log("Socket rooms:", rooms);
Namespaces
Creating namespaces
// Server: Create namespace
const chatNs = io.of("/chat");
const adminNs = io.of("/admin");
chatNs.on("connection", (socket) => {
console.log("Chat namespace connected");
socket.emit("welcome", "Welcome to chat");
});
adminNs.on("connection", (socket) => {
console.log("Admin namespace connected");
});
// Default namespace
io.of("/").on("connection", (socket) => {
// Main namespace
});
Client connecting to namespaces
// Default namespace
const socket = io("http://localhost:3000");
// Custom namespace
const chatSocket = io("http://localhost:3000/chat");
const adminSocket = io("http://localhost:3000/admin");
// Dynamic namespace
const dynamicNs = io(`http://localhost:3000/${namespace}`);
chatSocket.on("connect", () => {
console.log("Connected to chat namespace");
});
Broadcasting patterns
Broadcast types
// To all clients
io.emit("event", "data");
// To all except sender
socket.broadcast.emit("event", "data");
// To room
io.to("room1").emit("event", "data");
// To room except sender
socket.to("room1").emit("event", "data");
// To specific socket ID
io.to(socketId).emit("private", "data");
// To all in namespace
io.of("/chat").emit("event", "data");
Advanced broadcasting
// Volatile (ok if lost)
socket.volatile.emit("tick", Date.now());
// Compressed
socket.compress(true).emit("large", bigData);
// Binary data
socket.emit("image", buffer);
// To local clients only (no Redis broadcast)
io.local.emit("event", "data");
// Chain modifiers
socket.to("room1").compress(true).volatile.emit("fast", "data");
Targeting specific clients
// Get socket by ID
const targetSocket = io.sockets.sockets.get(socketId);
if (targetSocket) {
targetSocket.emit("private", "message");
}
// Broadcast to specific socket IDs
io.to([socketId1, socketId2]).emit("group", "data");
// Exclude specific sockets
socket.broadcast.except(socketId).emit("public", "data");
Acknowledgements
Server requesting acknowledgement
// Server sends with callback
io.on("connection", (socket) => {
socket.emit("question", "data", (response) => {
console.log("Client responded:", response);
});
// With timeout (v4.6+)
socket.timeout(5000).emit("request", "data", (err, response) => {
if (err) {
console.error("Timeout or error");
} else {
console.log("Response:", response);
}
});
});
Client responding
// Client receives and acknowledges
socket.on("question", (data, callback) => {
console.log("Server asked:", data);
// Send response back
callback({
status: "ok",
result: "processed",
});
});
// Client requesting acknowledgement
socket.emit("action", "data", (response) => {
console.log("Server responded:", response);
});
Connection handling
Connection lifecycle
// Server
io.on("connection", (socket) => {
console.log("Connected:", socket.id);
socket.on("disconnect", (reason) => {
console.log("Disconnected:", reason);
// Reasons: "server namespace disconnect",
// "client namespace disconnect", "ping timeout", etc.
});
socket.on("disconnecting", () => {
// Socket is about to disconnect
console.log("Rooms:", socket.rooms);
});
});
Manual disconnect
// Server disconnects client
socket.disconnect(); // Graceful disconnect
socket.disconnect(true); // Force disconnect
// Client disconnects
socket.disconnect();
// Client reconnects
socket.connect();
// Disable auto-reconnection
const socket = io({
autoConnect: false,
});
socket.connect(); // Manual connect
Middleware
Server middleware
// Namespace middleware
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (isValidToken(token)) {
socket.userId = getUserId(token);
next();
} else {
next(new Error("Authentication error"));
}
});
// Per-event middleware (v4.6+)
io.on("connection", (socket) => {
socket.use(([event, ...args], next) => {
console.log(`Event: ${event}`);
next();
});
});
// Error handling
io.use((socket, next) => {
try {
validateSocket(socket);
next();
} catch (error) {
next(error);
}
});
Client authentication
// Auth data in handshake
const socket = io({
auth: {
token: "jwt-token-here",
},
});
// Access in middleware
io.use((socket, next) => {
const token = socket.handshake.auth.token;
// Validate token
next();
});
// Dynamic auth
socket.auth = { token: "new-token" };
socket.connect();
Connection options
Server options
const io = new Server(server, {
cors: {
origin: "https://example.com",
methods: ["GET", "POST"],
},
// Connection timeout
connectTimeout: 45000,
// Ping interval/timeout
pingInterval: 25000,
pingTimeout: 20000,
// Max payload size
maxHttpBufferSize: 1e6,
// Allow upgrades to WebSocket
allowUpgrades: true,
// Transports allowed
transports: ["websocket", "polling"],
// Serve client files
serveClient: false,
});
Client options
const socket = io("http://localhost:3000", {
// Transports order
transports: ["websocket", "polling"],
// Auto-connect
autoConnect: true,
// Reconnection
reconnection: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
reconnectionAttempts: 5,
// Query parameters
query: {
userId: "123",
},
// Auth
auth: {
token: "jwt-token",
},
// Upgrade timeout
timeout: 20000,
});
Common patterns
Chat application
// Server
io.on("connection", (socket) => {
// User joins room
socket.on("join", (room, username) => {
socket.join(room);
socket.username = username;
// Notify room
socket.to(room).emit("userJoined", username);
// Send room history
socket.emit("history", getChatHistory(room));
});
// Receive message
socket.on("message", (room, message) => {
const msg = {
user: socket.username,
text: message,
timestamp: Date.now(),
};
// Broadcast to room
io.to(room).emit("message", msg);
// Save to database
saveChatMessage(room, msg);
});
// Typing indicator
socket.on("typing", (room) => {
socket.to(room).emit("userTyping", socket.username);
});
});
// Client
socket.emit("join", "general", "John");
socket.on("message", (msg) => {
displayMessage(msg);
});
document.getElementById("input").addEventListener("input", () => {
socket.emit("typing", currentRoom);
});
Real-time notifications
// Server: Send notification to user
function notifyUser(userId, notification) {
const userSockets = getUserSockets(userId);
userSockets.forEach((socketId) => {
io.to(socketId).emit("notification", notification);
});
}
// Track user sockets
io.on("connection", (socket) => {
socket.on("authenticate", (userId) => {
socket.userId = userId;
addUserSocket(userId, socket.id);
// Send unread notifications
socket.emit("notifications", getUnread(userId));
});
socket.on("disconnect", () => {
removeUserSocket(socket.userId, socket.id);
});
});
// Client: Display notifications
socket.on("notification", (notif) => {
showNotification(notif.title, notif.message);
updateBadgeCount();
});
Error handling
Server error handling
io.on("connection", (socket) => {
// Handle client errors
socket.on("error", (error) => {
console.error("Socket error:", error);
});
// Catch event handler errors
socket.on("riskyEvent", async (data) => {
try {
await processData(data);
socket.emit("success");
} catch (error) {
socket.emit("error", {
message: "Processing failed",
code: error.code,
});
}
});
});
// Middleware error
io.use((socket, next) => {
if (!socket.handshake.auth.token) {
return next(new Error("Authentication required"));
}
next();
});
// Client receives error
socket.on("connect_error", (error) => {
console.error("Connection failed:", error.message);
});
Client error handling
// Connection errors
socket.on("connect_error", (error) => {
if (error.message === "Authentication required") {
// Re-authenticate
socket.auth.token = getNewToken();
socket.connect();
}
});
// Timeout errors (v4.6+)
socket.timeout(5000).emit("data", data, (err, response) => {
if (err) {
console.error("Request timeout");
}
});
// Custom error events
socket.on("error", (error) => {
showErrorMessage(error.message);
});
// Reconnection events
socket.on("reconnect", (attemptNumber) => {
console.log("Reconnected after", attemptNumber, "attempts");
});
socket.on("reconnect_error", (error) => {
console.error("Reconnection failed");
});
socket.on("reconnect_failed", () => {
console.error("All reconnection attempts failed");
});
Testing
Server-side testing
// Using socket.io-client for testing
const io = require("socket.io-client");
const ioServer = require("socket.io");
describe("Socket.io server", () => {
let server, serverSocket, clientSocket;
beforeAll((done) => {
server = ioServer(3000);
server.on("connection", (socket) => {
serverSocket = socket;
});
clientSocket = io("http://localhost:3000");
clientSocket.on("connect", done);
});
afterAll(() => {
server.close();
clientSocket.disconnect();
});
test("should emit message", (done) => {
clientSocket.on("response", (data) => {
expect(data).toBe("received");
done();
});
serverSocket.emit("response", "received");
});
});
Mock client testing
// Create test client
function createTestSocket() {
const socket = io("http://localhost:3000", {
autoConnect: false,
auth: { token: "test-token" },
});
return socket;
}
// Test event emission
test("client sends message", (done) => {
const socket = createTestSocket();
socket.on("connect", () => {
socket.emit("message", "test");
socket.on("ack", (response) => {
expect(response).toBe("ok");
socket.disconnect();
done();
});
});
socket.connect();
});
TypeScript support
Server with types
import { Server, Socket } from "socket.io";
interface ServerToClientEvents {
message: (data: string) => void;
userJoined: (username: string) => void;
}
interface ClientToServerEvents {
join: (room: string, username: string) => void;
message: (room: string, text: string) => void;
}
interface InterServerEvents {
ping: () => void;
}
interface SocketData {
username: string;
userId: string;
}
const io = new Server<
ClientToServerEvents,
ServerToClientEvents,
InterServerEvents,
SocketData
>(server);
io.on("connection", (socket) => {
// socket.data is typed as SocketData
socket.data.username = "guest";
// Events are type-checked
socket.on("message", (room, text) => {
// room: string, text: string
io.to(room).emit("message", text);
});
});
Client with types
import { io, Socket } from "socket.io-client";
interface ServerToClientEvents {
message: (data: string) => void;
userJoined: (username: string) => void;
}
interface ClientToServerEvents {
join: (room: string, username: string) => void;
message: (room: string, text: string) => void;
}
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
"http://localhost:3000",
);
// Type-safe event handlers
socket.on("message", (data) => {
// data is typed as string
console.log(data);
});
// Type-safe emission
socket.emit("join", "general", "John");
Performance optimization
Volatile events
// Server: Skip if client not ready
socket.volatile.emit("tick", Date.now());
// Client: High-frequency updates
setInterval(() => {
socket.volatile.emit("position", {
x: player.x,
y: player.y,
});
}, 16); // 60fps, ok if some lost
Binary data
// Send buffer
const buffer = Buffer.from("binary data");
socket.emit("file", buffer);
// Receive buffer
socket.on("file", (buffer) => {
// buffer is Buffer type
console.log(buffer.length);
});
// Compression
socket.compress(true).emit("large", bigData);
Connection pooling
// Limit connections per IP
const connections = new Map();
io.use((socket, next) => {
const ip = socket.handshake.address;
const count = connections.get(ip) || 0;
if (count >= 5) {
return next(new Error("Too many connections"));
}
connections.set(ip, count + 1);
socket.on("disconnect", () => {
connections.set(ip, connections.get(ip) - 1);
});
next();
});
Redis adapter (Scaling)
Setup Redis adapter
// Install: npm install @socket.io/redis-adapter redis
import { Server } from "socket.io";
import { createAdapter } from "@socket.io/redis-adapter";
import { createClient } from "redis";
const io = new Server(server);
// Create Redis clients
const pubClient = createClient({ url: "redis://localhost:6379" });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
// Use adapter
io.adapter(createAdapter(pubClient, subClient));
// Now works across multiple servers
io.emit("event", "data"); // All servers receive
Redis adapter features
// Emit to all servers
io.emit("globalEvent", "data");
// Server-local only
io.local.emit("localEvent", "data");
// Get socket count across servers
const sockets = await io.allSockets();
console.log("Total connected:", sockets.size);
// Get sockets in room (all servers)
const roomSockets = await io.in("room1").allSockets();
// Utility methods
const count = await io.serverSideEmit("countRequest");
Gotchas
⚠️ Socket ID changes
// Socket ID changes on reconnection
socket.on("connect", () => {
console.log(socket.id); // Different each time!
});
// Use custom IDs for persistence
socket.on("authenticate", (userId) => {
socket.userId = userId; // Store custom ID
});
// Track across reconnections
const userSockets = new Map(); // userId -> Set of socket IDs
⚠️ Memory leaks with listeners
// BAD: Listener never removed
socket.on("data", handler);
// GOOD: Remove listener
socket.on("disconnect", () => {
socket.removeListener("data", handler);
});
// GOOD: Use once for one-time events
socket.once("init", (data) => {
// Auto-removed after first call
});
// Remove all listeners
socket.removeAllListeners();
⚠️ Rooms auto-cleanup
// Rooms are auto-removed when empty
socket.join("room1");
socket.disconnect();
// "room1" deleted if it was the only socket
// Check room exists before emitting
const sockets = await io.in("room1").fetchSockets();
if (sockets.length > 0) {
io.to("room1").emit("message", "data");
}
⚠️ Volatile vs regular events
// Regular: Guaranteed delivery (buffered if disconnected)
socket.emit("important", "data");
// Volatile: May be lost if client busy/disconnected
socket.volatile.emit("position", { x: 10, y: 20 });
// Use volatile for high-frequency, non-critical updates
// Use regular for important state changes
⚠️ Namespace separation
// Namespaces are completely separate
const chat = io.of("/chat");
const admin = io.of("/admin");
// This only emits to default namespace "/"
io.emit("event", "data");
// Emit to specific namespace
chat.emit("event", "data");
// Cross-namespace not supported
// Each client needs separate connections
⚠️ Acknowledgement order
// Callbacks execute BEFORE next event
socket.emit("first", "data", (response) => {
console.log("1. Ack received");
});
socket.emit("second", "data");
// Server may receive "second" before responding to "first"
// Use timeouts for critical flows
socket.timeout(5000).emit("critical", data, (err, res) => {
if (!err) {
socket.emit("next", data);
}
});
Version-specific features
v4.6+ features
// Timeout API
socket.timeout(5000).emit("request", data, (err, response) => {
if (err) {
console.error("Timeout");
}
});
// Socket#except() - exclude specific sockets
socket.broadcast.except(socketId).emit("event", "data");
// Server-side events
io.serverSideEmit("customEvent", "data");
io.on("customEvent", (data) => {
// Received on all servers
});
v4.0+ features
// Catch-all listeners
socket.onAny((eventName, ...args) => {
console.log(`Event: ${eventName}`, args);
});
socket.onAnyOutgoing((eventName, ...args) => {
console.log(`Sent: ${eventName}`, args);
});
// Disconnect other sockets
await socket.disconnectSockets();
await socket.in("room1").disconnectSockets();
// Fetch socket instances
const sockets = await io.fetchSockets();
const roomSockets = await io.in("room1").fetchSockets();
v3 to v4 migration
// v3: Broadcast to all
io.emit("event", "data");
// v4: Still works, but use explicit broadcast
io.emit("event", "data"); // Same behavior
socket.broadcast.emit("event", "data"); // Clearer intent
// v3: Rooms as array
socket.join(["room1", "room2"]);
// v4: Still works
socket.join("room1");
socket.join(["room1", "room2"]);
// v3: No TypeScript support
// v4: Full TypeScript support with generics
Also see
- Socket.io Documentation (socket.io)
- Socket.io Server API (socket.io)
- Socket.io Client API (socket.io)
- Socket.io Examples (github.com)
- Socket.io Redis Adapter (socket.io)
- Engine.IO Protocol (socket.io)
- WebSocket vs Socket.io (socket.io)