WebSockets
A ws route separates two phases, each with its own context:
- Upgrade phase — the middlewares run on the HTTP upgrade request, with a regular HTTP
Context. Respond through it (c.status(401).respond()...) to reject the connection. If no middleware responds, Mousse upgrades automatically. - Connected phase — the handler runs once the connection is established, with a
WSContext: the socket is already open,c.send()works immediately — just asssehandlers receive an already-sustained context.
app.ws('/chat/:room', (c) => {
// c is a WSContext: already connected
c.subscribe(c.param('room'));
c.send(`welcome to ${c.param('room')}`);
c.onMessage((ws, message) => {
c.publish(c.param('room'), message);
});
});Rejecting an upgrade
Anything you would do in a get handler (read headers, query, check auth, respond) belongs in a middleware of the ws route:
app.ws('/private',
(c) => {
// HTTP Context of the upgrade request
if (!c.getHeader('authorization'))
return c.status(401).respond(); // upgrade rejected
},
(c) => {
// WSContext: only reached when no middleware responded
c.send('authenticated and connected');
});µWebSockets.js only accepts upgrades on patterns registered through its ws router — a plain
getroute cannot upgrade. Upgrade middlewares are the supported way to run HTTP logic before a connection.
WSContext
The connected context exposes the socket, the original request data (snapshotted at upgrade time), and event registration:
| Member | Description |
|---|---|
c.send(message, isBinary?, compress?) |
Send to this socket, backpressure handled (see below) |
c.subscribe(topic) / c.unsubscribe(topic) |
µWS pub/sub topics |
c.publish(topic, message, ...) |
Publish to this route's subscribers, excluding the sender |
c.mousse.publish(topic, message, ...) |
Publish app-wide (all ws routes), including the sender |
c.end(code?, message?) |
Gracefully close the connection |
c.params, c.param(), c.query, c.headers, c.getHeader(), c.url, c.route |
Data of the original upgrade request |
c.on(event, listener) / c.off(event) + shortcuts below |
Event registration |
c.socket |
The native µWS socket, for advanced use |
c.log(data) |
Route logger, no-op when none is set |
Topic scoping: µWebSockets.js keeps one topic tree per ws route.
c.publishtherefore only reaches subscribers of the same route pattern (and skips the sender) — the classic chat-room case. For broadcasting across routes, or including the sender, usec.mousse.publish.
Events
Register listeners synchronously inside the handler — a listener registered after an await can miss early events:
| Shortcut | Signature | Fired |
|---|---|---|
onMessage |
(ws, message, isBinary?) |
Message received |
onClose |
(ws, code, message) |
Connection closed |
onPing / onPong |
(ws, message) |
Ping/pong received |
onDrain |
(ws) |
Backpressure drained |
onDropped |
(ws, message, isBinary?) |
Message dropped because of backpressure |
onSubscription |
(ws, topic, newCount, oldCount) |
Pub/sub subscription change |
One listener per event: registering twice replaces the first. The ws argument is the native socket; in practice you rarely need it since the listener closes over c:
c.onMessage((ws, message) => c.send(message)); // echoBackpressure
c.send never overflows a slow client: when the socket buffer exceeds the route's maxBackPressure (default 16 KiB), messages are queued and flushed automatically, in order, on drain.
app.ws('/feed', { maxBackPressure: 64 * 1024 }, (c) => { /* ... */ });Error handling
- Errors thrown by upgrade middlewares go through the route's
httpErrorHandler. Without one, anHTTPErrorrejects the upgrade with its status and{ error: message }, anything else with a500— like any HTTP route. - Errors thrown by the handler or inside event listeners go through the route's
wsErrorHandler, orconsole.errorwhen none is set:
app.ws('/chat', { wsErrorHandler: { handle: (error) => report(error) } }, (c) => { /* ... */ });