Skip to main content

Best npm Packages for Realtime in 2026

·PkgPulse Team

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.

Compare realtime package downloads on PkgPulse.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.