mrkeyoor.com_
Wed 05 Aug 05:02 UTC
npmWeb Backendupdated 05 Aug 2026

socket.io

socket.io is an event-based realtime server for Node.js. It runs its own protocol on top of Engine.IO, giving you WebSocket transport with an HTTP long-polling fallback, automatic reconnection, rooms for broadcasting to groups of clients, namespaces for splitting one server into channels, and per-event acknowledgements. Browsers connect with the matching socket.io-client package; it is not a plain WebSocket server and plain WebSocket clients cannot talk to it.

Verdict

Still the fastest path to rooms, reconnection, and fallback transports in Node, and the v4 API has been calm for years. Pick plain ws when you control every client and can afford to rebuild the conveniences, and think twice on serverless.

API stability4/5The v4 API has been stable since 2021 and 4.8.x releases are maintenance-grade. The v2 to v3 protocol break was painful and old clients still cannot connect to new servers.
Docs5/5The socket.io docs site has a full v4 reference, an emit cheatsheet, adapter guides, and a dedicated troubleshooting section for connection issues. The GitHub README is just a pointer to it.
Maintenance3/5Pushed in July 2026 and issues get triaged, but output is mostly patch releases on 4.x, and activity is concentrated in a single primary maintainer.
Ecosystem5/5About 17 million weekly downloads, official and community clients across many languages, and adapters for Redis, MongoDB, and Postgres for multi-node setups.

Use it if

  • You need rooms, broadcasts, and presence semantics out of the box for chat, live dashboards, or collaborative features
  • Your clients sit behind corporate proxies or strict firewalls and you want long-polling fallback plus reconnection handled for you
  • You want request-response semantics over a persistent connection: acknowledgements and emitWithAck with timeouts are built in
Skip it if

Setup reality

npm install socket.io and attach it to an HTTP server; the basic case works in minutes. The friction points: CORS must be configured explicitly on the server or browser clients fail with opaque connection errors; the client and server major versions must match because v2, v3, and v4 protocols are incompatible; and the moment you run more than one node you need sticky sessions at the load balancer plus an adapter (Redis is the usual one) or long-polling requests land on the wrong instance. The docs troubleshooting guide exists because these failure modes are common.

Patterns

Create a server and echo eventsbasic-server

import { createServer } from 'http';
import { Server } from 'socket.io';

const httpServer = createServer();
const io = new Server(httpServer, {
  cors: { origin: 'https://app.example.com' },
});

io.on('connection', (socket) => {
  socket.on('chat message', (msg) => {
    io.emit('chat message', msg); // to everyone, sender included
  });
});

httpServer.listen(3000);

Without the cors option, browser clients on another origin fail during the handshake with a generic xhr poll error.

Connect from the browser and handle reconnectsclient-connect

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

const socket = io('https://api.example.com', {
  auth: { token: 'abc' },
});

socket.on('connect', () => console.log(socket.id));
socket.on('disconnect', (reason) => console.log('lost:', reason));
socket.io.on('reconnect', (attempt) => console.log('back after', attempt));

Reconnection is on by default; socket.id changes after every reconnect, so never use it as a stable user identifier.

Join a room and emit to itrooms-broadcast

io.on('connection', (socket) => {
  socket.join('room-42');

  // everyone in the room, including this socket
  io.to('room-42').emit('update', payload);

  // everyone in the room EXCEPT this socket
  socket.to('room-42').emit('user joined', socket.id);
});

io.to() includes the sender, socket.to() excludes it; mixing them up is the classic duplicate-message bug.

Split the server into namespacesnamespaces

const adminNs = io.of('/admin');

adminNs.on('connection', (socket) => {
  adminNs.emit('hello', 'admins only');
});

// client side
const adminSocket = io('https://api.example.com/admin');

Namespaces share one underlying connection; rooms exist per namespace, so 'room-42' in /admin is not 'room-42' in /.

Authenticate connections with middlewareauth-middleware

io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  verifyToken(token)
    .then((user) => {
      socket.data.user = user;
      next();
    })
    .catch(() => next(new Error('unauthorized')));
});

Errors passed to next() reach the client as a connect_error event; anything on socket.data survives for the connection lifetime.

Get a response for an emitted eventacknowledgements

// client
const response = await socket
  .timeout(5000)
  .emitWithAck('create-order', { sku: 'x1' });

// server
socket.on('create-order', async (data, callback) => {
  const order = await createOrder(data);
  callback({ ok: true, id: order.id });
});

Without .timeout(), a server that never calls the callback leaves the client promise hanging forever.

Broadcast to everyone except the senderbroadcast-except-sender

io.on('connection', (socket) => {
  socket.on('typing', (user) => {
    socket.broadcast.emit('typing', user);
  });
});

socket.broadcast covers the whole namespace; chain .to(room) to narrow it to one room.

Clean up on disconnect and inspect the reasondisconnect-handling

io.on('connection', (socket) => {
  socket.on('disconnect', (reason) => {
    // e.g. 'transport close', 'ping timeout', 'server namespace disconnect'
    removeFromPresence(socket.data.user, reason);
  });
});

A brief network blip fires disconnect then a fresh connection event; debounce presence updates or users flicker offline.

Scale across multiple nodes with Redisscale-redis-adapter

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

const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);

const io = new Server(httpServer, {
  adapter: createAdapter(pubClient, subClient),
});

The adapter routes broadcasts between nodes, but you still need sticky sessions at the load balancer for long-polling clients.

Type events end to end in TypeScripttyped-events

interface ClientToServerEvents {
  'chat message': (msg: string) => void;
}
interface ServerToClientEvents {
  'chat message': (msg: string) => void;
}

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

io.on('connection', (socket) => {
  socket.on('chat message', (msg) => io.emit('chat message', msg));
});

The generics only type your own code; nothing validates payloads at runtime, so still parse untrusted input.

Alternatives

PackageRegistryPick it when
wsnpmYou control both ends, want a bare standards-compliant WebSocket server, and can build reconnection yourself
pusher-jsnpmYou would rather pay for managed realtime infrastructure than run and scale your own socket servers