| name | socket-io |
| description | [Applies to: **/*.{js,jsx,ts,tsx}] Definitive guidelines for building robust, performant, and maintainable real-time applications using Socket.IO v4.x, emphasizing modern JavaScript practices and common pitfalls. |
| source | cursor_mdc |
Socket.IO Best Practices
Socket.IO v4.x is the bedrock for real-time communication in our applications. This guide establishes the definitive standards for its usage, focusing on reliability, performance, and maintainability.
1. Core Initialization & Security
Always initialize Socket.IO clients with explicit, secure, and efficient options.
✅ GOOD: Secure & Explicit Client Initialization
Use wss:// in production, enable CORS on the server for cross-origin clients, and avoid forceNew unless strictly necessary.
import { io } from "socket.io-client";
const socket = io("wss://api.yourdomain.com", {
path: "/socket.io/",
transports: ["websocket", "polling"],
autoConnect: true,
auth: {
token: "YOUR_AUTH_TOKEN",
},
query: {
clientId: "unique-client-id",
},
});
const express = require("express");
const http = require("http");
const { Server } = require("socket.io");
const app = express();
const server = http.createServer(app);
const ioServer = new Server(server, {
cors: {
origin: "https://yourclientdomain.com",
methods: ["GET", "POST"],
credentials: true,
},
path: "/socket.io/",
});
ioServer.on("connection", (socket) => {
console.log(`Client connected: ${socket.id}`);
});
❌ BAD: Insecure or Inefficient Initialization
Avoid http:// in production, implicit options, and unnecessary forceNew.
const socket = io("http://localhost:3000");
const anotherSocket = io("https://api.yourdomain.com", { forceNew: true });
2. Code Organization: Namespaces & Rooms
Structure your real-time logic using namespaces and rooms to isolate concerns and manage communication efficiently.
✅ GOOD: Logical Separation with Namespaces and Rooms
Use namespaces for distinct application features (e.g., /chat, /admin) and rooms for grouping clients within a namespace (e.g., roomId: 'general', userId: 'user123').
const chatNamespace = ioServer.of("/chat");
chatNamespace.use((socket, next) => {
if (socket.handshake.auth.token === "valid-chat-token") {
next();
} else {
next(new Error("Authentication error for chat namespace"));
}
});
chatNamespace.on("connection", (socket) => {
console.log(`User ${socket.id} connected to /chat`);
socket.on("joinRoom", (roomName) => {
socket.join(roomName);
socket.emit("roomJoined", `You joined ${roomName}`);
chatNamespace.to(roomName).emit("userJoined", `${socket.id} joined ${roomName}`);
});
socket.on("sendMessage", ({ room, message }) => {
chatNamespace.to(room).emit("newMessage", { : socket., message });
});
});
chatSocket = (, {
: { : },
});
chatSocket.(, {
chatSocket.(, );
});
chatSocket.(, .(, data));
❌ BAD: Monolithic Event Handling
Avoid dumping all events into the default namespace without logical grouping.
ioServer.on("connection", (socket) => {
socket.on("chatMessage", () => {
});
socket.on("adminAction", () => {
});
});
3. Robust Error Handling & Reconnection
Socket.IO provides automatic reconnection, but you must handle connection errors and customize retry strategies for production.
✅ GOOD: Comprehensive Error & Reconnection Handling
Listen for connect_error to diagnose issues and reconnect_attempt to customize back-off strategies.
socket.on("connect_error", (err) => {
console.error("Connection error:", err.message);
if (err.message === "Authentication error") {
console.error("Authentication failed, stopping reconnection attempts.");
socket.disconnect();
}
});
socket.on("reconnect_attempt", (attemptNumber) => {
console.log(`Reconnection attempt ${attemptNumber}`);
});
socket.on("reconnect", (attemptNumber) => {
console.log(`Reconnected after ${attemptNumber} attempts.`);
});
socket.on("disconnect", (reason) => {
console.log("Disconnected:", reason);
if (reason === "io server disconnect") {
}
});
❌ BAD: Ignoring Connection State
Neglecting error events leads to silent failures and poor user experience.
socket.on("connect", () => console.log("Connected"));
4. Payload Hygiene & Acknowledgements
Keep payloads lean, avoid circular references, and use acknowledgements for critical data delivery guarantees.
✅ GOOD: Efficient Payloads & Guaranteed Delivery
Send only necessary data. For critical operations, use acknowledgements.
socket.emit("createOrder", { productId: "P123", quantity: 2 }, (response) => {
if (response.success) {
console.log("Order created successfully:", response.orderId);
} else {
console.error("Failed to create order:", response.error);
}
});
ioServer.on("connection", (socket) => {
socket.on("createOrder", (orderData, callback) => {
try {
const newOrder = processOrder(orderData);
callback({ success: true, orderId: newOrder.id });
} catch (error) {
callback({ success: false, error: error.message });
}
});
});
❌ BAD: Bloated Payloads & Unreliable Delivery
Sending large, unoptimized data or assuming delivery for critical actions.
const userSession = { };
socket.emit("updateSession", userSession);
ioServer.on("connection", (socket) => {
socket.on("updateSession", (sessionData) => {
JSON.stringify(sessionData);
});
});
5. Performance & Scalability
Optimize for network efficiency and design for horizontal scaling.
✅ GOOD: Minimal Data & Scalable Design
Send only deltas, use binary for large data, and consider a Redis adapter for multi-server deployments.
const imageBuffer = new ArrayBuffer(1024);
socket.emit("uploadImage", imageBuffer);
const { createAdapter } = require("@socket.io/redis-adapter");
const { createClient } = require("redis");
const pubClient = createClient({ url: "redis://localhost:6379" });
const subClient = pubClient.duplicate();
Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
ioServer.adapter(createAdapter(pubClient, subClient));
ioServer.emit("globalEvent", "This reaches all clients across all servers");
});
❌ BAD: Inefficient Data Transfer & Monolithic Servers
Sending full state updates repeatedly or running a single server without an adapter for high-traffic scenarios.
const gameState = { };
setInterval(() => {
socket.emit("gameStateUpdate", gameState);
}, 16);
6. Type Safety with TypeScript
Always use TypeScript to define clear contracts for events and payloads, preventing runtime errors.
✅ GOOD: Strongly Typed Event Definitions
Define interfaces for event names and their corresponding payloads.
export interface ServerToClientEvents {
noArg: () => void;
basicEmit: (a: number, b: string, c: Buffer) => void;
withAck: (d: string, callback: (e: number) => void) => void;
roomJoined: (roomName: string) => void;
newMessage: (data: { user: string; message: string }) => void;
}
export interface ClientToServerEvents {
hello: () => void;
createOrder: (orderData: { productId: string; quantity: number }, callback: (response: { success: boolean; orderId?: string; error?: string }) => void) => void;
: ;
: ;
}
{
: ;
}
{
: ;
: ;
}
import { Server } from "socket.io";
import { ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData } from "./types/socket";
const ioServer = new Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>(3000);
ioServer.on("connection", (socket) => {
socket.data.userId = "abc";
socket.on("createOrder", (orderData, callback) => {
console.log(orderData.productId);
callback({ success: true, orderId: "ORD123" });
});
});
import { io, Socket } from "socket.io-client";
import { ClientToServerEvents, ServerToClientEvents } from ;
: <, > = ();
socket.(, {
.();
});
socket.(, { : , : }, {
(response.) {
.(response.);
}
});
❌ BAD: Untyped Events
Using plain JavaScript or any types for events, leading to potential runtime errors and difficult debugging.