Skip to main content

Best npm Packages for Realtime in 2026

·PkgPulse Team
0

Socket.io has been in 11,000+ npm packages since 2010. PartyKit runs real-time logic on Cloudflare's edge network using Durable Objects — every "room" has persistent state close to users globally. Ably guarantees message delivery with a distributed network and message history. The realtime stack has matured: self-hosted (Socket.io), edge-native (PartyKit), and managed services (Ably, Pusher) each serve different use cases in 2026.

TL;DR

Socket.io for self-hosted realtime in Node.js — the most-used library, battle-tested, excellent ecosystem, works with Redis for horizontal scaling. PartyKit for multiplayer, collaborative features, and stateful rooms on Cloudflare's edge network — each room runs in a Durable Object with persistent state. Ably for managed realtime at scale with guaranteed message delivery, message history, and presence. Pusher for simple pub/sub when you want a fully managed service with predictable pricing. For most new Node.js applications, Socket.io remains the pragmatic default — for edge/collaborative applications, PartyKit is worth evaluating.

Key Takeaways

  • Socket.io: 4M+ weekly downloads, self-hosted, automatic fallback (WebSocket → polling), namespace/rooms, Redis adapter for horizontal scaling
  • PartyKit: Cloudflare Durable Objects, persistent state per room, edge-global, WebSocket + fetch APIs
  • Ably: Managed service, guaranteed delivery, message history, presence tracking, global CDN
  • Pusher: Managed service, 100 connections free, simple pub/sub, Channels and Beams (push notifications)
  • Server-Sent Events (SSE): Native browser API, good for one-way real-time (like AI streaming)
  • WebSocket native: Use ws package (50M+ downloads) for raw WebSocket without Socket.io overhead
  • All: Work alongside Hono, Express, Fastify for REST + realtime in one app

Choosing Your Realtime Architecture

Before picking a library, decide on the architecture:

One-way server → client:
  SSE (Server-Sent Events) — built into browsers, no library needed
  Good for: AI streaming, live dashboards, notifications

Two-way bidirectional:
  Socket.io / ws — self-hosted WebSocket
  Good for: chat, gaming, collaborative editing (small scale)

Two-way + state per room:
  PartyKit — Cloudflare Durable Objects
  Good for: multiplayer, collaborative docs, presence

Managed pub/sub at scale:
  Ably / Pusher — hosted service
  Good for: production apps without realtime infrastructure

Socket.io

Package: socket.io (server) + socket.io-client (browser) Weekly downloads: 4M+ (server), 8M+ (client) GitHub stars: 60K Creator: Guillermo Rauch (original), maintained by open-source community

Socket.io is the de facto standard for Node.js realtime. It wraps WebSocket with automatic fallback to HTTP long-polling, rooms, namespaces, and a simple event-based API.

Installation

npm install socket.io      # Server
npm install socket.io-client  # Client (or use CDN)

Basic Chat Example

// server/index.ts
import { createServer } from 'http';
import { Server } from 'socket.io';

const httpServer = createServer();
const io = new Server(httpServer, {
  cors: {
    origin: 'http://localhost:3000',
    methods: ['GET', 'POST'],
  },
});

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

  // Join a room
  socket.on('join-room', (roomId: string) => {
    socket.join(roomId);
    socket.to(roomId).emit('user-joined', { userId: socket.id });
  });

  // Message in room
  socket.on('message', (data: { room: string; text: string }) => {
    io.to(data.room).emit('message', {
      userId: socket.id,
      text: data.text,
      timestamp: Date.now(),
    });
  });

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

httpServer.listen(3000);
// client/app.ts
import { io } from 'socket.io-client';

const socket = io('http://localhost:3000');

socket.on('connect', () => {
  console.log('Connected:', socket.id);
  socket.emit('join-room', 'general');
});

socket.on('message', (data) => {
  console.log(`${data.userId}: ${data.text}`);
});

socket.emit('message', { room: 'general', text: 'Hello!' });

Horizontal Scaling with Redis

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

const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();

await Promise.all([pubClient.connect(), subClient.connect()]);

io.adapter(createAdapter(pubClient, subClient));

// Now events emitted to rooms reach all server instances
// io.to('room').emit() works across multiple Node.js processes

Namespaces and Rooms

// Namespaces: separate logical connections
const chatNS = io.of('/chat');
const gameNS = io.of('/game');

chatNS.on('connection', (socket) => {
  // Only receives events from /chat namespace
});

gameNS.on('connection', (socket) => {
  // Only receives events from /game namespace
});

// Rooms: groups within a namespace
socket.join('room-123');
io.to('room-123').emit('event', data);    // Everyone in room
socket.to('room-123').emit('event', data); // Everyone except sender
socket.broadcast.emit('event', data);      // Everyone except sender

Typed Events (TypeScript)

interface ServerToClientEvents {
  'message': (data: { userId: string; text: string; timestamp: number }) => void;
  'user-joined': (data: { userId: string }) => void;
}

interface ClientToServerEvents {
  'join-room': (roomId: string) => void;
  'message': (data: { room: string; text: string }) => void;
}

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

// Client
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io('...');

Socket.io Limitations

  • Adds overhead vs raw WebSocket (protocol envelope, fallback logic)
  • Requires sticky sessions for horizontal scaling without Redis adapter
  • Not native to edge runtimes (designed for Node.js)

ws — Raw WebSocket

Package: ws Weekly downloads: 50M+ GitHub stars: 21K

When you don't need Socket.io's rooms, namespaces, and fallbacks — just raw WebSocket:

npm install ws
npm install -D @types/ws
import { WebSocketServer, WebSocket } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', (ws) => {
  ws.on('message', (message) => {
    // Broadcast to all connected clients
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(message.toString());
      }
    });
  });

  ws.send(JSON.stringify({ type: 'welcome', message: 'Connected!' }));
});

Use ws when you want minimal overhead and are building the protocol layer yourself.

Server-Sent Events (SSE)

SSE is a browser-native one-way streaming API — perfect for AI responses, live feeds, and notifications where the client doesn't send data:

// Hono SSE example
import { Hono } from 'hono';
import { streamSSE } from 'hono/streaming';

const app = new Hono();

app.get('/stream', async (c) => {
  return streamSSE(c, async (stream) => {
    // Stream AI response chunks
    const response = await openai.chat.completions.create({
      model: 'gpt-4o',
      messages: [{ role: 'user', content: 'Hello' }],
      stream: true,
    });

    for await (const chunk of response) {
      const text = chunk.choices[0]?.delta?.content || '';
      await stream.writeSSE({ data: text });
    }
  });
});
// Browser: native EventSource API (no library needed)
const eventSource = new EventSource('/stream');
eventSource.onmessage = (event) => {
  appendText(event.data);
};

SSE is simpler than WebSocket for one-way streaming — no library needed on client or server, automatic reconnection built into the browser.

PartyKit

Package: partykit + partysocket Creator: Sunil Pai (PartyKit, acquired by Cloudflare 2024)

PartyKit runs on Cloudflare's edge network. Each "party" (room) runs in a Durable Object — a single-threaded, globally-coordinated process with persistent state. If 1,000 users are in the same room, all their connections hit the same Durable Object wherever it's located geographically.

Installation

npm install partykit partysocket

Party Server (Runs on Cloudflare)

// party/chat.ts
import type * as Party from 'partykit/server';

export default class ChatRoom implements Party.Server {
  messages: Array<{ user: string; text: string }> = [];

  constructor(readonly room: Party.Room) {}

  onConnect(conn: Party.Connection) {
    // Send existing messages to new connection
    conn.send(JSON.stringify({ type: 'history', messages: this.messages }));
  }

  onMessage(message: string, sender: Party.Connection) {
    const parsed = JSON.parse(message);

    if (parsed.type === 'chat') {
      const msg = { user: sender.id, text: parsed.text };
      this.messages.push(msg);

      // Broadcast to all connections in this room
      this.room.broadcast(JSON.stringify({ type: 'message', ...msg }), [sender.id]);
    }
  }

  onClose(conn: Party.Connection) {
    // Connection closed
  }
}

Party Client (Browser)

import PartySocket from 'partysocket';

const socket = new PartySocket({
  host: 'my-app.username.partykit.dev',
  room: 'chat-room-123',
});

socket.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  if (data.type === 'message') {
    addMessage(data.user, data.text);
  }
});

socket.send(JSON.stringify({ type: 'chat', text: 'Hello!' }));

Persistent State

export default class GameRoom implements Party.Server {
  // state persists across connections — Durable Object hibernation
  gameState: GameState = { players: {}, score: {} };

  async onRequest(req: Party.Request) {
    // HTTP endpoint for this room
    if (req.method === 'GET') {
      return Response.json(this.gameState);
    }
  }

  onConnect(conn: Party.Connection) {
    // State is available immediately — persisted in Durable Object storage
    conn.send(JSON.stringify({ type: 'state', data: this.gameState }));
  }
}

Managed Services: Ably and Pusher

For production applications where you don't want to manage WebSocket infrastructure:

Ably

npm install ably
import Ably from 'ably';

const client = new Ably.Realtime({ key: process.env.ABLY_API_KEY });
const channel = client.channels.get('general');

// Publish
await channel.publish('message', { text: 'Hello!' });

// Subscribe
channel.subscribe('message', (msg) => {
  console.log(msg.data.text);
});

// Presence (who's online)
await channel.presence.enter({ name: 'Alice' });
const members = await channel.presence.get();

Key Ably features: guaranteed delivery (at-least-once), message history (rewind), presence, global CDN across 200+ PoPs.

Pusher Channels

npm install pusher pusher-js
// Server (Node.js)
import Pusher from 'pusher';

const pusher = new Pusher({
  appId: process.env.PUSHER_APP_ID,
  key: process.env.PUSHER_KEY,
  secret: process.env.PUSHER_SECRET,
  cluster: 'us2',
});

await pusher.trigger('my-channel', 'my-event', { message: 'Hello!' });
// Client (Browser)
import Pusher from 'pusher-js';

const pusher = new Pusher(process.env.NEXT_PUBLIC_PUSHER_KEY, { cluster: 'us2' });
const channel = pusher.subscribe('my-channel');

channel.bind('my-event', (data) => {
  console.log(data.message);
});

Comparison

ToolSelf-hostedEdgePersistent stateManagedFree tier
Socket.ioYesNoNo (need Redis)NoN/A
wsYesLimitedNoNoN/A
PartyKitNo (Cloudflare)YesYes (DO)Yes1GB storage
AblyNoYes (200+ PoPs)Message historyYes200 connections
PusherNoYesNoYes100 connections

Choose Socket.io: Self-hosted Node.js realtime, chat, gaming, collaborative tools — full control, battle-tested.

Choose PartyKit: Multiplayer, collaborative apps on Cloudflare edge, stateful rooms, modern edge-native architecture.

Choose Ably: Production scale with guaranteed delivery, message history, and presence — enterprise features without managing infrastructure.

Choose Pusher: Simple pub/sub with minimal setup, generous enough free tier for prototypes and small apps.

Use SSE: One-way streaming (AI responses, live dashboards) — native browser support, no library needed.

Horizontal Scaling and Connection Management in Production

Running Socket.io in production on a single Node.js process is straightforward, but horizontal scaling introduces challenges that developers often underestimate. When you have multiple server instances behind a load balancer, a WebSocket connection established to instance A cannot receive events emitted on instance B unless you configure a shared adapter. The @socket.io/redis-adapter is the standard solution — all instances share a Redis pub/sub channel, and io.to('room').emit() propagates across the cluster automatically. For load balancers, you must enable sticky sessions (connection affinity) so that the HTTP upgrade request for a WebSocket connection always routes to the same server instance. Without sticky sessions, the Socket.io polling fallback will fail intermittently because consecutive HTTP requests from the same client hit different servers. AWS ALB supports sticky sessions via the stickiness.type attribute; Nginx requires the ip_hash directive in the upstream block.

Security Considerations for WebSocket Applications

WebSocket connections bypass many of the security controls that HTTP middleware applies by default. CORS configuration on the Socket.io server controls which origins can establish connections, but the Authorization header is not sent in WebSocket upgrade requests — implement token authentication in the Socket.io handshake using the auth option on the client and validate the token in the io.use() middleware on the server before the connection is accepted. For PartyKit's Durable Objects, all connections route through Cloudflare's edge network, which provides DDoS protection automatically, but you still need to validate any session tokens sent in the WebSocket message body or as URL query parameters in onConnect. Ably and Pusher handle authentication through signed tokens generated server-side — never expose your secret API key in client-side code. Always implement rate limiting per connection or per user to prevent individual clients from flooding your rooms with messages and degrading performance for other connected users.

TypeScript Type Safety Across the Socket Event Contract

Socket.io v4's generic types allow you to define the full event contract between client and server in a single shared TypeScript file, making it impossible to emit an event on one side without a corresponding handler on the other. The ServerToClientEvents and ClientToServerEvents interfaces enforce event names and payload shapes at compile time — socket.emit('mesage', data) (typo) becomes a TypeScript error rather than a silent no-op at runtime. For monorepo setups with shared packages, extract these event type definitions into a @yourapp/socket-types package that both the frontend and backend import. PartyKit's TypeScript support is similarly first-class — the Party.Server interface ensures your party class implements the required lifecycle methods, and the Party.Connection type provides typed access to connection metadata. For SSE-based realtime, the EventSource browser API is untyped by default; define a custom typed wrapper around EventSource that narrows event.data to your known message shapes using Zod parsing on receipt.

Pricing Models and Cost Projections at Scale

Choosing between self-hosted and managed realtime services requires modeling the cost at your expected connection count. Socket.io with a Redis adapter on a small VPS (two t3.small instances + a Redis cache.t3.micro) runs around $50/month and handles several thousand concurrent connections, but your engineering team absorbs the operational overhead of uptime monitoring, failover, and Redis backup. Ably's pricing is connection-hours based — 200 concurrent connections for 720 hours (one month) consumes 144,000 connection-hours, which falls in their Growth tier at around $30/month. Pusher's 100-connection free tier suits prototypes but the jump to 1,000 connections puts you at $49/month. PartyKit, acquired by Cloudflare in 2024, charges based on Durable Object CPU time and storage reads/writes — for typical collaborative apps with moderate activity, costs are negligible at small scale but grow with room state persistence operations. Model your connection count, messages per second, and room count against each provider's pricing calculator before committing.

Compare realtime package downloads on PkgPulse.

See also: Best npm Packages for Real-Time Features 2026 and Best WebSocket Libraries for Node.js in 2026, Best Realtime 2026: Socket.io vs Ably vs Pusher.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.