mrkeyoor.com_
Sat 19 Sept 15:50 UTC
npmWeb Backendupdated 19 Sept 2026

socket.io review

socket.io 4.8.3 is the Node server for the Socket.IO event protocol. It sits on Engine.IO, begins with HTTP long polling when necessary, upgrades to WebSocket when possible, and gives application code named events, acknowledgements, rooms, namespaces, middleware, broadcasts, and reconnection state. A `socket.io-client` peer speaks its packet format; a generic WebSocket client does not. The 4.8.3 release makes repeated shutdown safe by preventing `io.close()` from throwing after the server has already stopped. Our install loaded through both CommonJS and ESM. The package belongs on a persistent backend, since its transports, heartbeat, room membership, and optional recovery state all assume a process that remains available after the HTTP handshake.

Verdict

socket.io 4.8.3 installed in 1.9 seconds, used 6 MB across 22 packages, loaded through CommonJS and ESM, and returned 0 audit findings in our sandbox; its browser bundle failed because this is server code. Use it when controlled clients need its rooms, acknowledgements, fallback, and reconnect protocol, and use plain WebSocket tooling when protocol interoperability matters.

We installed it

Lab card: what happened when we installed socket.ioScreenshot of socket.io documentation
Install✓ · 1.9s22 packages on disk · 6 MB
ImportESM import works · require() works · CommonJS package with exports map
Browsern/acould not be bundled for the browser (Node-only code, most likely)
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does socket.io install cleanly?

Yes. In a fresh container with an empty cache, npm install socket.io finished in 2 seconds, leaving 22 packages and 6 MB on disk. npm audit reported no known vulnerabilities.

Can socket.io run in a browser?

Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.

Does socket.io work with both ESM and CommonJS?

Yes. Both import 'socket.io' and require('socket.io') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does socket.io include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

socket.io or ws: which should you use?

ws: Choose it for standards-based WebSocket frames when the application can define reconnects, heartbeats, rooms, and message semantics. socket.io 4.8.3 installed in 1.9 seconds, used 6 MB across 22 packages, loaded through CommonJS and ESM, and returned 0 audit findings in our sandbox; its browser bundle failed because this is server code.

When should you not use socket.io?

Third-party clients must connect with the standard WebSocket protocol. Socket.IO uses an additional handshake and packet format that generic WebSocket libraries do not understand.

API stability4/5The 4.x server still centers on `Server`, `connection`, `socket.on`, `emit`, rooms, namespaces, middleware, broadcasts, and acknowledgements. Version 4.8.3 changes only repeated shutdown behavior: `io.close()` no longer throws when the server is already stopped. That narrow correction is a good signal within the current major. Compatibility still includes the Socket.IO and Engine.IO protocol generations, so rolling upgrades should test the actual client majors in service.
Docs5/5The versioned v4 site documents server and client setup, event delivery, acknowledgement timeouts, rooms, namespaces, middleware, TypeScript generics, CORS, reverse proxies, sticky sessions, adapters, connection-state recovery, and connection troubleshooting. Deployment pages explain why polling changes load-balancer requirements. The repository README mostly redirects to those pages, so readers need the docs version selector open when search results surface v2 or v3 examples.
Maintenance4/5GitHub reports an unarchived monorepo pushed on 2026-07-24, with 198 open issues and pull requests combined. Server 4.8.3 shipped on 2026-03-18 with the shutdown fix, while Engine.IO, adapters, parsers, and clients continue to publish from the same repository. The server release itself is conservative, which reduces churn, though maintenance signals must be read across several interdependent packages instead of one changelog.
Ecosystem5/5npm counted 17,967,990 downloads in the completed week ending 2026-08-24, and GitHub reports 63,206 stars. Official JavaScript clients and community implementations give the event protocol broad reach, while adapters cover common cross-node backplanes. That ecosystem is specific to Socket.IO. A standards-only WebSocket client, proxy, or debugging tool sees a different protocol and cannot participate without Socket.IO support.

Use it if

  • Your own web or mobile clients need rooms, named events, acknowledgements, and reconnection behavior under one protocol.
  • Some networks require HTTP long-polling fallback before a WebSocket upgrade can succeed.
  • Per-namespace middleware and room broadcasts map directly to tenant, document, game, or channel boundaries.
  • The deployment can keep connections open and provide sticky routing plus a shared adapter when it runs more than one node.
Skip it if

Setup reality

We installed socket.io 4.8.3 in a fresh Node 22 Bookworm sandbox. npm finished in 1.9 seconds, left 22 packages and 6 MB on disk, and reported 0 known vulnerabilities at critical, high, moderate, and low severity. The server package declares 7 direct dependencies, 0 peer dependencies, and 1452 KB unpacked. It carries the MIT license and includes TypeScript declarations.

The package is CommonJS with an exports map. require() and ESM import both worked in our sandbox. Our esbuild browser build failed on Node-only code, which confirms that this package stays on the server. Browser code installs socket.io-client. A plain WebSocket object cannot replace that client because it lacks the Socket.IO handshake, framing, acknowledgements, and namespace rules.

Attach Server to the HTTP or HTTPS server that owns the port. Browser origins need an explicit CORS policy, especially when cookies or credentials cross origins. Authentication commonly reads socket.handshake.auth in namespace middleware. Validate the token before room membership, store the resulting user identity in socket.data, and validate each event payload at runtime. The connection's socket.id may change after reconnection.

One Node process keeps rooms in memory. Several processes need a compatible adapter for cross-node broadcasts. If long polling remains enabled, the load balancer also needs sticky routing so every request from one Engine.IO session reaches the same node. Acknowledgements need timeouts because a handler may never call its callback. Connection-state recovery has a bounded window and adapter requirements, so clients still need a full resync path even though our 1.9-second install was uneventful.

Patterns

Attach Socket.IO to an HTTP server attach-http-server

import { createServer } from "node:http";
import { Server } from "socket.io";

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

io.on("connection", (socket) => {
  socket.emit("ready", { connectionId: socket.id });
});

httpServer.listen(3000);

The HTTP server owns the listening port. Credentialed browser requests require a specific allowed origin; `*` cannot be paired with credentials.

Reject an unauthenticated namespace connection authenticate-handshake

io.use(async (socket, next) => {
  try {
    const user = await verifyAccessToken(socket.handshake.auth.token);
    socket.data.userId = user.id;
    next();
  } catch {
    next(new Error("unauthorized"));
  }
});

Calling `next(error)` produces `connect_error` on the client. Authenticate before joining rooms, then authorize each state-changing event separately.

Validate data before changing state validate-event-payload

import { z } from "zod";

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

io.on("connection", (socket) => {
  socket.on("message:create", async (input, acknowledge) => {
    const parsed = Message.safeParse(input);
    if (!parsed.success) return acknowledge({ ok: false, code: "bad_payload" });
    const message = await saveMessage(socket.data.userId, parsed.data);
    acknowledge({ ok: true, message });
  });
});

Socket.IO's TypeScript event declarations disappear at runtime. Every network payload still needs shape checks and authorization before a write.

Join a room and broadcast to its members join-room

io.on("connection", (socket) => {
  socket.on("document:open", async (documentId) => {
    await requireDocumentAccess(socket.data.userId, documentId);
    await socket.join(`document:${documentId}`);
    io.to(`document:${documentId}`).emit("presence:joined", {
      userId: socket.data.userId,
    });
  });
});

`io.to(room)` includes the socket that just joined. Start the broadcast with `socket.to(room)` when the sender already updated its own UI.

Send an update only to peers exclude-sender

socket.on("cursor:move", ({ documentId, x, y }) => {
  socket.to(`document:${documentId}`).volatile.emit("cursor:moved", {
    userId: socket.data.userId,
    x,
    y,
  });
});

`socket.to(room)` excludes the sender. `volatile` also permits dropping the event when the transport is not writable, which fits replaceable cursor positions.

Bound an acknowledgement wait acknowledge-with-timeout

try {
  const result = await socket
    .timeout(5_000)
    .emitWithAck("order:create", { sku: "ink-42", quantity: 2 });
  console.log(result.orderId);
} catch {
  console.error("server did not acknowledge within 5 seconds");
}

The receiver must call its acknowledgement callback on every handled path. A timeout rejects the promise; it does not prove whether the server committed the operation.

Give administrators a separate namespace separate-namespace

const admin = io.of("/admin");

admin.use(requireAdmin);
admin.on("connection", (socket) => {
  socket.on("jobs:list", async (acknowledge) => {
    acknowledge(await listJobs());
  });
});

Each namespace has separate middleware, rooms, sockets, and broadcasts. A room name used under `/admin` does not refer to the same room in `/`.

Type client and server event names type-event-contract

interface ClientEvents {
  "message:create": (
    input: { roomId: string; text: string },
    ack: (result: { ok: boolean }) => void
  ) => void;
}
interface ServerEvents {
  "message:created": (message: { id: string; text: string }) => void;
}

const io = new Server<ClientEvents, ServerEvents>(httpServer);

The generics catch local spelling and signature mistakes during compilation. They do not validate packets created by a compromised or outdated client.

Recover a short interrupted session enable-state-recovery

const io = new Server(httpServer, {
  connectionStateRecovery: {
    maxDisconnectionDuration: 120_000,
    skipMiddlewares: false,
  },
});

io.on("connection", (socket) => {
  socket.emit("recovery:status", { recovered: socket.recovered });
});

Recovery works only inside the 120-second window and depends on adapter support. Keeping middleware enabled rechecks authentication; expired sessions need a full state fetch.

Share broadcasts through Redis install-redis-adapter

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

const publisher = createClient({ url: process.env.REDIS_URL });
const subscriber = publisher.duplicate();
await Promise.all([publisher.connect(), subscriber.connect()]);
io.adapter(createAdapter(publisher, subscriber));

The adapter carries broadcasts and room operations between Socket.IO nodes. Polling requests still require sticky routing at the load balancer.

Read sockets in a room across the adapter inspect-room-members

const sockets = await io.in("support:priority").fetchSockets();

for (const remoteSocket of sockets) {
  console.log(remoteSocket.id, remoteSocket.data.userId);
}

With a cluster adapter, `fetchSockets()` can query other nodes and may involve network work. Do not run it on every high-frequency event.

Close the server during shutdown shutdown-idempotently

async function shutdown() {
  await new Promise((resolve) => io.close(resolve));
}

process.once("SIGTERM", shutdown);
process.once("SIGINT", shutdown);

Version 4.8.3 fixes a throw when `io.close()` is called after the server has already stopped. Application shutdown should still register handlers once and close other resources explicitly.

Alternatives

PackageRegistryPick it when
wsnpmChoose it for standards-based WebSocket frames when the application can define reconnects, heartbeats, rooms, and message semantics.
engine.ionpmChoose the transport and upgrade layer alone when you want polling fallback while designing the higher-level event protocol yourself.
@fastify/websocketnpmChoose it when a Fastify service needs WebSocket routes integrated with Fastify hooks and request handling.

More web backend guides

urllib3 · requests · ws · anyio · undici · httpx · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.