uwebsockets
v3.0.6
Published
Fastest uWebSockets server for Node.js – µWebSockets outperforms Socket.IO & Fastify. Built in C++ for high-performance networking & pub/sub.
Downloads
706
Maintainers
Keywords
Readme
:zap: Simple performance
µWebSockets.js is a web server bypass for Node.js that reimplements eventing, networking, encryption, web protocols, routing and pub/sub in highly optimized C++. As such, µWebSockets.js delivers web serving for Node.js, 8.5x that of Fastify and at least 10x that of Socket.IO. It is also the built-in web server of Bun.
- Browse the documentation and see the main repo. There are tons of examples but here's the gist of it all:
Example 1 (Basic Echo & HTTP): Ideal for simple, single-instance applications that just need basic WebSocket echoing and HTTP serving.
💡 Note on
.App()vs.SSLApp():
- Use
.App()to create a standard, unencrypted server (http://andws://). This is standard for local testing or when deploying behind a cloud load balancer (like AWS or Render) that handles SSL termination for you.- Use
.SSLApp()(shown below) to create a secure, encrypted server (https://andwss://). You only need this if your Node.js server is directly exposed to the public internet and you must manually provide SSL certificates (key.pemandcert.pem).
/* Non-SSL is simply App() */
require("uwebsockets")
.SSLApp({
/* There are more SSL options, cut for brevity */
key_file_name: "misc/key.pem",
cert_file_name: "misc/cert.pem",
})
.ws("/*", {
/* There are many common helper features */
idleTimeout: 32,
maxBackpressure: 1024,
maxPayloadLength: 512,
compression: DEDICATED_COMPRESSOR_3KB,
/* For brevity we skip the other events (upgrade, open, ping, pong, close) */
message: (ws, message, isBinary) => {
/* You can do app.publish('sensors/home/temperature', '22C') kind of pub/sub as well */
/* Here we echo the message back, using compression if available */
let ok = ws.send(message, isBinary, true);
},
})
.get("/*", (res, req) => {
/* It does Http as well */
res
.writeStatus("200 OK")
.writeHeader("IsExample", "Yes")
.end("Hello there!");
})
.listen(9001, (listenSocket) => {
if (listenSocket) {
console.log("Listening to port 9001");
}
});Example 2 (Authentication & Local Rooms): Great for medium-scale, single-instance applications (like a local multiplayer game or small chat app) that require query parameter authentication and room-based broadcasting.
const uWS = require("uwebsockets");
const WS_INSTANCES = {};
// Below is a sample UWS server that listens for WebSocket connections and handles messages
// from clients. It also includes a simple authentication mechanism based on a query parameter.
// This is a basic example and should be adapted to your specific use case and security requirements.
// This example uses uwebsockets for WebSocket handling.
// You can install uwebsockets using npm:
// npm install uwebsockets
// Note: Make sure to replace the placeholder code with your actual logic.
const wsApp = uWS.App().ws("/*", {
// Options for WebSocket server
compression: 0, // No compression
maxPayloadLength: 16 * 1024 * 1024, // 16 MB max payload size
idleTimeout: 8, // 8 seconds idle timeout
upgrade: (res, req, context) => {
// Check if the request has a valid "channel_name" query parameter
const channel_name = req.getQuery("channel_name");
// Check if the channel_name is valid (you can implement your own validation logic). below is an example
if (!channel_name) {
// Invalid channel_name, send a 400 Bad Request response
res
.writeStatus("400 Bad Request! missing channel_name in query parameter")
.end();
return;
}
// Store the channel_name in the WebSocket connection
res.upgrade(
{ channel_name },
req.getHeader("sec-websocket-key"),
req.getHeader("sec-websocket-protocol"),
req.getHeader("sec-websocket-extensions"),
context
);
},
open: (ws) => {
// Check if the channel_name is already connected
const channel_name = ws.channel_name;
// Store the WebSocket connection in the WS_INSTANCES object
WS_INSTANCES[channel_name] = WS_INSTANCES[channel_name] || [];
WS_INSTANCES[channel_name].push(ws);
// Send a welcome message to the client
const welcomePayload = {
event: "welcome",
message: `Welcome ${channel_name} to the WebSocket server!`,
timestamp: Date.now(),
};
ws.send(JSON.stringify(welcomePayload));
},
message: (ws, message) => {
try {
// Parse the message and handle it
const channel_name = ws.channel_name;
// Buffer.from(message).toString() is used to convert the message to a string
// This is necessary because uwebsockets may send binary messages
// If you are sending text messages, you can directly use message.toString()
// If you are sending binary messages, you may need to convert them to a string first, Buffer.from(message).toString()
const msg = Buffer.from(message).toString();
const parsedMsg = JSON.parse(msg);
// Payload to be sent to the channel_name
// You can customize the payload structure based on your requirements
// For example, you can include the channel_name, event type, and data
const payload = {
event: "broadcast",
from: channel_name,
data: parsedMsg,
timestamp: Date.now(),
};
// Example: Echo the message back to the same user (or broadcast as needed)
ws.send(JSON.stringify(payload));
} catch (error) {
// Handle errors that occur during message processing
// For example, if the message is not a valid JSON string or if there is an error in your logic
// You can send an error message back to the client
// and log the error for debugging purposes
// Buffer.from(message).toString() is used to convert the message to a string
// This is necessary because uwebsockets may send binary messages
// If you are sending text messages, you can directly use message.toString()
const msg = Buffer.from(message).toString();
// error payload to be sent to the channel_name
// You can customize the error payload structure based on your requirements
const errorPayload = {
event: "error",
from: ws.channel_name,
message: error.message,
data: JSON.stringify(msg),
timestamp: Date.now(),
};
// Send the error message back to the client
ws.send(JSON.stringify(errorPayload));
console.error("Error processing message:", {
error,
message: JSON.stringify(msg),
ws: ws.channel_name,
});
}
},
close: (ws) => {
// Handle WebSocket disconnection
const channel_name = ws.channel_name;
// Remove the WebSocket connection from the WS_INSTANCES object
// This is done by filtering out the closed connection from the array of connections for the channel_name
// If there are no more connections for the channel_name, delete the channel_name entry from WS_INSTANCES
// This ensures that the server does not keep track of closed connections
// and helps to free up resources
WS_INSTANCES[channel_name] =
WS_INSTANCES[channel_name]?.filter((conn) => conn !== ws) || [];
if (WS_INSTANCES[channel_name].length === 0) delete WS_INSTANCES[channel_name];
},
});
// Start the WebSocket server
// Listen using the properly defined wsApp instance
wsApp.listen(9001, (token) => {
if (token) {
console.log(`WebSocket listening on ws://localhost:${9001}`);
} else {
console.error(`Failed to listen on port ${9001}`);
}
});
// Note: Make sure to handle errors and edge cases in your production code.
// This example is a basic starting point and should be adapted to your specific use case.
// You can also implement additional features such as authentication, authorization, and message validation
// based on your requirements.
// For more information on uwebsockets, refer to the official documentationExample 3 (Massive Scale with Redis Sharded Pub/Sub): The architecture required for highly scalable, globally distributed applications like WhatsApp, Discord, or Snapchat.
const uWS = require("uwebsockets");
const Redis = require("ioredis"); // npm install ioredis
const WS_INSTANCES = {};
// Setup Redis Publisher and Subscriber for bridging
// Option 1: Standalone Setup
const redisConfig = {
host: "127.0.0.1",
port: 6379,
// username: "my_username", // Optional: uncomment if your Redis requires a username
// password: "my_password" // Optional: uncomment if your Redis requires a password
};
const redisPub = new Redis(redisConfig);
const redisSub = new Redis(redisConfig);
// Option 2: Cluster Setup
// If you are using a Redis Cluster, comment out the standalone setup above and uncomment this block:
/*
const clusterNodes = [
{ host: "127.0.0.1", port: 6379 },
{ host: "127.0.0.1", port: 6380 },
{ host: "127.0.0.1", port: 6381 },
];
const clusterOptions = {
redisOptions: {
// username: "my_username", // Optional: uncomment if your Redis requires a username
// password: "my_password" // Optional: uncomment if your Redis requires a password
}
};
const redisPub = new Redis.Cluster(clusterNodes, clusterOptions);
const redisSub = new Redis.Cluster(clusterNodes, clusterOptions);
*/
// Listen for sharded messages from Redis and send to valid WebSocket instances
redisSub.on("smessage", (channel, message) => {
try {
// The channel argument tells us exactly which chat room this message is for!
if (WS_INSTANCES[channel]) {
WS_INSTANCES[channel].forEach((ws) => {
ws.send(message);
});
}
} catch (error) {
console.error("Error processing Redis message:", error);
}
});
const wsApp = uWS.App().ws("/*", {
compression: 0,
maxPayloadLength: 16 * 1024 * 1024,
idleTimeout: 8,
upgrade: (res, req, context) => {
const channel_name = req.getQuery("channel_name");
const user = req.getQuery("user");
if (!channel_name || !user) {
res.writeStatus("400 Bad Request! missing channel_name or user in query parameter").end();
return;
}
res.upgrade({ channel_name, user }, req.getHeader("sec-websocket-key"), req.getHeader("sec-websocket-protocol"), req.getHeader("sec-websocket-extensions"), context);
},
open: (ws) => {
const channel_name = ws.channel_name;
// If this is the first local user in this channel, subscribe to it in Redis
if (!WS_INSTANCES[channel_name]) {
WS_INSTANCES[channel_name] = [];
redisSub.ssubscribe(channel_name, (err) => {
if (err) console.error(`Failed to subscribe to ${channel_name}:`, err);
});
}
WS_INSTANCES[channel_name].push(ws);
ws.send(JSON.stringify({ event: "welcome", message: `Welcome ${ws.user} to ${channel_name}!` }));
},
message: (ws, message) => {
try {
const msg = Buffer.from(message).toString();
const parsedMsg = JSON.parse(msg);
// Publish the incoming message to Redis instead of echoing directly.
// This bridges the message across all Node.js instances!
const payload = {
event: "broadcast",
from: ws.user,
channel_name: parsedMsg.channel_name, // specify this if you want targeted delivery
data: parsedMsg,
timestamp: Date.now(),
};
// Uses spublish for Sharded Pub/Sub. ioredis automatically hashes channel_name to distribute load!
redisPub.spublish(ws.channel_name, JSON.stringify(payload));
} catch (error) {
ws.send(JSON.stringify({ event: "error", message: error.message }));
}
},
close: (ws) => {
const channel_name = ws.channel_name;
WS_INSTANCES[channel_name] = WS_INSTANCES[channel_name]?.filter((conn) => conn !== ws) || [];
// If no local users are left in this channel, unsubscribe from Redis to save resources
if (WS_INSTANCES[channel_name].length === 0) {
delete WS_INSTANCES[channel_name];
redisSub.sunsubscribe(channel_name, (err) => {
if (err) console.error(`Failed to unsubscribe from ${channel_name}:`, err);
});
}
},
});
wsApp.listen(9001, (token) => {
if (token) {
console.log("WebSocket listening on ws://localhost:9001");
}
});Supported Node Versions
- Oldest supported Node.js version:
16 - Supported Node.js versions:
16,18,20,21,22,24,25 - Not supported:
17,19,23
Supported Docker Images
node:16-bullseye,node:16-bullseye-slimnode:18-bullseye,node:18-bullseye-slimnode:20-bullseye,node:20-bullseye-slim,node:20-bookworm,node:20-bookworm-slim,node:20-trixie,node:20-trixie-slimnode:21-bookworm,node:21-bookworm-slim,node:21-trixie,node:21-trixie-slimnode:22-trixie,node:22-trixie-slimnode:24-trixie,node:24-trixie-slimnode:25-trixie,node:25-trixie-slim
Local Ubuntu / Linux Support
- Linux support depends on
glibc, not only Node.js version - Node
16,18,20,21work with the bundled older Linux builds - Node
22,24,25Linux builds requireglibc >= 2.38 - Ubuntu
22.04uses olderglibc, so Node22,24,25may fail there - Ubuntu
24.04or newer is recommended for Node22,24,25
