Skip to main content

Best WebSocket Libraries for Node.js in 2026

·PkgPulse Team

TL;DR

ws for raw performance; Socket.io for full-featured real-time apps. ws (~80M weekly downloads) is the minimal, fast WebSocket implementation — Node.js's de facto standard. Socket.io (~10M downloads) wraps ws with rooms, namespaces, auto-reconnect, and fallback to HTTP long-polling. uWebSockets.js is the raw performance king — handles 10x the connections of ws with lower latency. Pick based on whether you need Socket.io's features or raw throughput.

Key Takeaways

  • ws: ~80M weekly downloads — minimal, fast, what most WebSocket libs are built on
  • Socket.io: ~10M downloads — rooms, namespaces, auto-reconnect, HTTP fallback
  • uWebSockets.js — highest throughput, C++ bindings, 10x faster than ws
  • Socket.io v4+ — HTTP/2, WebTransport support, CORS built-in
  • Ably/Pusher — managed WebSocket services (no infra to maintain)

ws (Raw, Fast)

// ws — WebSocket server
import { WebSocketServer, WebSocket } from 'ws';
import http from 'http';

const server = http.createServer();
const wss = new WebSocketServer({ server });

wss.on('connection', (ws, req) => {
  const clientId = generateId();
  console.log(`Client connected: ${clientId}`);

  ws.on('message', (data) => {
    const message = JSON.parse(data.toString());

    // Broadcast to all connected clients
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify({
          from: clientId,
          ...message,
        }));
      }
    });
  });

  ws.on('close', () => {
    console.log(`Client disconnected: ${clientId}`);
  });

  ws.on('error', (error) => {
    console.error(`WebSocket error from ${clientId}:`, error);
  });

  // Send welcome message
  ws.send(JSON.stringify({ type: 'connected', clientId }));
});

server.listen(8080, () => console.log('WS server on :8080'));
// ws — client with reconnection (ws doesn't include this)
class ReconnectingWebSocket {
  private ws: WebSocket | null = null;
  private reconnectAttempts = 0;
  private maxReconnectDelay = 30000;

  constructor(private url: string) {
    this.connect();
  }

  private connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      console.log('Connected');
      this.reconnectAttempts = 0;
    };

    this.ws.onclose = () => {
      const delay = Math.min(
        1000 * Math.pow(2, this.reconnectAttempts++),
        this.maxReconnectDelay
      );
      console.log(`Disconnected. Reconnecting in ${delay}ms...`);
      setTimeout(() => this.connect(), delay);
    };

    this.ws.onmessage = (event) => {
      this.onMessage(JSON.parse(event.data));
    };
  }

  send(data: object) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data));
    }
  }

  onMessage(data: any) {
    // Override in subclass
  }
}

// Socket.io — server with rooms and namespaces
import { Server } from 'socket.io';
import { createServer } from 'http';
import express from 'express';

const app = express();
const httpServer = createServer(app);

const io = new Server(httpServer, {
  cors: {
    origin: 'https://app.example.com',
    methods: ['GET', 'POST'],
  },
  // HTTP long-polling fallback (automatic)
  transports: ['websocket', 'polling'],
});

// Namespace: /chat
const chatNS = io.of('/chat');

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

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

  // Broadcast to room
  socket.on('send-message', ({ roomId, message }) => {
    // Emit to all in room EXCEPT sender
    socket.to(roomId).emit('new-message', {
      from: socket.id,
      message,
      timestamp: Date.now(),
    });

    // Emit to ALL in room INCLUDING sender
    io.of('/chat').to(roomId).emit('message-sent', { messageId: generateId() });
  });

  // Acknowledge pattern (request/response over WebSocket)
  socket.on('ping', (data, callback) => {
    callback({ pong: true, serverTime: Date.now() });
  });

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

httpServer.listen(3000);
// Socket.io — client (browser)
import { io } from 'socket.io-client';

const socket = io('https://api.example.com/chat', {
  // Auto-reconnect (built-in)
  reconnectionDelayMax: 10000,
  auth: {
    token: localStorage.getItem('authToken'),
  },
});

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

socket.on('new-message', ({ from, message, timestamp }) => {
  renderMessage({ from, message, timestamp });
});

socket.on('disconnect', () => {
  console.log('Disconnected — auto-reconnecting...');
});

// Send with acknowledgment
socket.emit('ping', { test: true }, (response) => {
  console.log('Server response:', response); // { pong: true, serverTime: ... }
});
// Socket.io — middleware (authentication)
io.use(async (socket, next) => {
  const token = socket.handshake.auth.token;
  try {
    const user = await verifyJWT(token);
    socket.data.user = user; // Attach to socket
    next();
  } catch (err) {
    next(new Error('Authentication failed'));
  }
});

// Now access in handlers
io.on('connection', (socket) => {
  const { user } = socket.data;
  console.log(`Authenticated user: ${user.email}`);
});

uWebSockets.js (High Performance)

// uWebSockets.js — 10x throughput of ws, C++ bindings
import { App, SHARED_COMPRESSOR } from 'uWebSockets.js';

const app = App({});

app.ws('/chat/:room', {
  compression: SHARED_COMPRESSOR,
  maxPayloadLength: 16 * 1024,  // 16KB
  idleTimeout: 60,              // 60 seconds

  open(ws) {
    const room = ws.getUserData().room || 'general';
    ws.subscribe(room); // Pub/sub rooms built-in
    console.log('Client connected');
  },

  message(ws, message, isBinary) {
    // Publish to all subscribers in room
    const room = ws.getUserData().room;
    app.publish(room, message, isBinary);
  },

  close(ws, code, message) {
    console.log('Client disconnected:', code);
  },
})
.listen(9001, (token) => {
  if (token) console.log('Listening to port 9001');
});

Performance Comparison

LibraryConnections/secLatency (p99)Memory/1K clients
uWebSockets.js~100K~1ms~60MB
ws~10K~5ms~300MB
Socket.io (ws)~5K~8ms~400MB
Socket.io (polling)~2K~50ms~500MB

Benchmarks are approximate. Real numbers depend on message size and hardware.


When to Choose

ScenarioPick
Chat app, notifications, presenceSocket.io
Need rooms + namespaces out of the boxSocket.io
HTTP long-polling fallback for mobileSocket.io
Building a protocol on top of WebSocketsws
10K+ concurrent connections, low latencyuWebSockets.js
Massively multiplayer game serveruWebSockets.js
Managed service (no infra)Ably or Pusher
Next.js server actions real-timeAbly (serverless-friendly)

Compare WebSocket library package health on PkgPulse.

Comments

Stay Updated

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