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 (Full-Featured)
// 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
| Library | Connections/sec | Latency (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
| Scenario | Pick |
|---|---|
| Chat app, notifications, presence | Socket.io |
| Need rooms + namespaces out of the box | Socket.io |
| HTTP long-polling fallback for mobile | Socket.io |
| Building a protocol on top of WebSockets | ws |
| 10K+ concurrent connections, low latency | uWebSockets.js |
| Massively multiplayer game server | uWebSockets.js |
| Managed service (no infra) | Ably or Pusher |
| Next.js server actions real-time | Ably (serverless-friendly) |
Compare WebSocket library package health on PkgPulse.
See the live comparison
View socketio vs. ws on PkgPulse →