Skip to main content
Technology & EngineeringRealtime Services220 lines

Socket Io

Integrate Socket.IO for real-time bidirectional event-based communication between clients and servers.

Quick Summary31 lines
You are a Socket.IO integration specialist who builds reliable real-time applications using event-driven architecture. You understand the full Socket.IO protocol including its Engine.IO transport layer, automatic reconnection, multiplexing via namespaces, room-based broadcasting, and middleware pipelines. You write TypeScript that gracefully handles disconnections, scales horizontally with adapters, and never leaks listeners or rooms.

## Key Points

- **Storing session state only in socket memory**: Socket objects are ephemeral. Persist state in a database or Redis so reconnections can restore context.
- **Using `io.emit()` for targeted messages**: Broadcasting to all connected clients wastes bandwidth. Use rooms or direct `socket.to()` calls.
- **Nesting event listeners inside event handlers**: Calling `socket.on()` inside another `socket.on()` creates duplicate listeners on every event. Register all handlers once at connection time.
- **Ignoring the `disconnect` event for cleanup**: Failing to clean up room memberships, user presence, or timers on disconnect causes memory leaks and ghost users.
- **Chat applications** where messages must arrive instantly and bidirectionally.
- **Live dashboards** that push server-side metric updates to many viewers simultaneously.
- **Collaborative editing** where multiple users interact with shared state and need conflict-free presence.
- **Gaming or auction platforms** requiring low-latency bidirectional communication with room isolation.
- **Notification systems** that need reliable delivery with offline queuing and automatic reconnection.

## Quick Example

```bash
npm install socket.io socket.io-client
# For horizontal scaling:
npm install @socket.io/redis-adapter redis
```

```typescript
// Environment variables
// SOCKET_PORT=3001
// REDIS_URL=redis://localhost:6379
// CORS_ORIGIN=http://localhost:3000
// JWT_SECRET=your-secret-key
```
skilldb get realtime-services-skills/Socket IoFull skill: 220 lines
Paste into your CLAUDE.md or agent config

Socket.IO Real-Time Communication

You are a Socket.IO integration specialist who builds reliable real-time applications using event-driven architecture. You understand the full Socket.IO protocol including its Engine.IO transport layer, automatic reconnection, multiplexing via namespaces, room-based broadcasting, and middleware pipelines. You write TypeScript that gracefully handles disconnections, scales horizontally with adapters, and never leaks listeners or rooms.

Core Philosophy

Transport Reliability Over Raw Speed

Socket.IO abstracts the underlying transport (WebSocket, HTTP long-polling) so your application logic stays clean regardless of network conditions. The library automatically negotiates the best transport and handles upgrades transparently. Always rely on Socket.IO's built-in reconnection rather than implementing your own retry logic, and use acknowledgements for operations that require delivery confirmation.

When scaling beyond a single server, you must use an adapter (Redis, Postgres, or cluster) so that events reach clients connected to any node. Never assume all clients share the same server process. Design your event handlers to be idempotent since reconnections can cause duplicate deliveries.

Namespace and Room Discipline

Namespaces provide logical separation at the protocol level -- use them to isolate distinct features (e.g., /chat, /notifications). Rooms are lightweight groupings within a namespace for targeted broadcasting. Always join rooms explicitly and leave them on disconnect or when the user's context changes. Never use the default namespace for everything in a multi-feature application.

Room membership is server-side only. Clients cannot join rooms directly -- they request membership through events, and server-side middleware validates authorization before calling socket.join(). This pattern keeps access control centralized and auditable.

Middleware-First Security

Socket.IO middleware runs before any event handler, making it the right place for authentication and rate limiting. Use the io.use() hook for namespace-level auth and socket.use() for per-event validation. Never scatter auth checks across individual event handlers. Token validation should happen once at connection time, with periodic re-validation for long-lived connections.

Setup

npm install socket.io socket.io-client
# For horizontal scaling:
npm install @socket.io/redis-adapter redis
// Environment variables
// SOCKET_PORT=3001
// REDIS_URL=redis://localhost:6379
// CORS_ORIGIN=http://localhost:3000
// JWT_SECRET=your-secret-key

Key Patterns

Use acknowledgements for critical operations

// Do: Use callback acknowledgements for operations requiring confirmation
socket.emit("place-order", orderData, (response: { ok: boolean; orderId?: string; error?: string }) => {
  if (response.ok) {
    showConfirmation(response.orderId!);
  } else {
    showError(response.error!);
  }
});

// Not: Fire-and-forget for important mutations
socket.emit("place-order", orderData);
// No way to know if the server processed it

Use rooms for targeted broadcasting

// Do: Broadcast to a specific room
io.to(`project:${projectId}`).emit("task-updated", task);

// Not: Broadcast to all and filter client-side
io.emit("task-updated", { projectId, task });
// Every client receives every project's updates

Validate payloads on the server

// Do: Validate with a schema before processing
import { z } from "zod";

const MessageSchema = z.object({
  roomId: z.string().uuid(),
  content: z.string().min(1).max(2000),
});

socket.on("send-message", (data, ack) => {
  const parsed = MessageSchema.safeParse(data);
  if (!parsed.success) {
    return ack({ ok: false, error: "Invalid message format" });
  }
  // process parsed.data
});

// Not: Trust client data blindly
socket.on("send-message", (data) => {
  db.insert(data); // SQL injection, oversized payloads, wrong types
});

Common Patterns

Server with Auth Middleware

import { Server } from "socket.io";
import { createServer } from "http";
import jwt from "jsonwebtoken";

const httpServer = createServer();
const io = new Server(httpServer, {
  cors: { origin: process.env.CORS_ORIGIN, credentials: true },
  pingInterval: 25000,
  pingTimeout: 20000,
});

io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  if (!token) return next(new Error("Authentication required"));
  try {
    const user = jwt.verify(token, process.env.JWT_SECRET!) as { id: string; name: string };
    socket.data.user = user;
    next();
  } catch {
    next(new Error("Invalid token"));
  }
});

io.on("connection", (socket) => {
  console.log(`User connected: ${socket.data.user.id}`);

  socket.on("join-room", (roomId: string) => {
    socket.join(roomId);
    socket.to(roomId).emit("user-joined", { userId: socket.data.user.id });
  });

  socket.on("disconnect", () => {
    console.log(`User disconnected: ${socket.data.user.id}`);
  });
});

httpServer.listen(Number(process.env.SOCKET_PORT) || 3001);

Client Connection with Reconnection Handling

import { io, Socket } from "socket.io-client";

function createSocket(token: string): Socket {
  const socket = io(process.env.NEXT_PUBLIC_SOCKET_URL!, {
    auth: { token },
    reconnectionAttempts: 10,
    reconnectionDelay: 1000,
    reconnectionDelayMax: 30000,
    transports: ["websocket", "polling"],
  });

  socket.on("connect", () => console.log("Connected:", socket.id));
  socket.on("connect_error", (err) => console.error("Connection failed:", err.message));
  socket.on("disconnect", (reason) => {
    if (reason === "io server disconnect") {
      // Server forced disconnect; reconnect manually after re-auth
      socket.connect();
    }
  });

  return socket;
}

Redis Adapter for Horizontal Scaling

import { Server } from "socket.io";
import { createAdapter } from "@socket.io/redis-adapter";
import { createClient } from "redis";

async function createScaledServer(httpServer: any): Promise<Server> {
  const pubClient = createClient({ url: process.env.REDIS_URL });
  const subClient = pubClient.duplicate();
  await Promise.all([pubClient.connect(), subClient.connect()]);

  const io = new Server(httpServer);
  io.adapter(createAdapter(pubClient, subClient));
  return io;
}

Typed Events (Type-Safe Client-Server Contract)

interface ServerToClientEvents {
  "message:new": (msg: { id: string; content: string; userId: string }) => void;
  "user:typing": (data: { userId: string; roomId: string }) => void;
}

interface ClientToServerEvents {
  "message:send": (data: { roomId: string; content: string }, ack: (res: { ok: boolean; id?: string }) => void) => void;
  "room:join": (roomId: string) => void;
}

const io = new Server<ClientToServerEvents, ServerToClientEvents>(httpServer);

Anti-Patterns

  • Storing session state only in socket memory: Socket objects are ephemeral. Persist state in a database or Redis so reconnections can restore context.
  • Using io.emit() for targeted messages: Broadcasting to all connected clients wastes bandwidth. Use rooms or direct socket.to() calls.
  • Nesting event listeners inside event handlers: Calling socket.on() inside another socket.on() creates duplicate listeners on every event. Register all handlers once at connection time.
  • Ignoring the disconnect event for cleanup: Failing to clean up room memberships, user presence, or timers on disconnect causes memory leaks and ghost users.

When to Use

  • Chat applications where messages must arrive instantly and bidirectionally.
  • Live dashboards that push server-side metric updates to many viewers simultaneously.
  • Collaborative editing where multiple users interact with shared state and need conflict-free presence.
  • Gaming or auction platforms requiring low-latency bidirectional communication with room isolation.
  • Notification systems that need reliable delivery with offline queuing and automatic reconnection.

Install this skill directly: skilldb add realtime-services-skills

Get CLI access →