Mousse

Let us play with bubbles

All of the soap, none of the opera.

A typed Node.js framework built on top of µWebSockets, for HTTP, WebSockets and Server-Sent Events.

Get started
pnpm add @moussejs/core
import { Mousse } from '@moussejs/core';

new Mousse()
	.get('/hello', () => 'Hello World !')
	.get('/users/:id', (c) => ({ id: c.param('id') }))
	.post('/echo', (c) => c.body())
	.ws('/chat', (c) => {
		c.onMessage((ws, message) => { ws.send(message); });
	})
	.sse('/events', (c) => c.send('welcome'))
	.listen(3000);

One way to write all of it

Routes, parameters, composable routers. Return a value or answer yourself. Read more

import { Mousse, Router, HTTPError } from '@moussejs/core';

const users = new Router()
	.use((c) => {
		if (!c.getHeader('authorization'))
			return c.status(401).respond();   // nothing else runs
	})
	.get('/:id', (c) => {
		if (c.param('id') === '0')
			throw new HTTPError(404, 'No such user');
		return { id: c.param('id') };         // sent as json
	});

new Mousse()
	.use('/users', users)
	.setDefaultHandler({ handle: (c) => c.status(404).respond('Nothing here') })
	.listen(3000);

A request has a clear path

Each stage can end the request, and nothing after it runs. What is cheap to refuse is refused first : a client over its limit never costs you a schema validation, and your authentication runs before a body gets parsed.

How requests are handled
  1. Request hooks Block an address, tag a request
    can stop here : any status
  2. Rate limiter Counted before anything is parsed
    can stop here : 429
  3. Body limit Declared size, then received bytes
    can stop here : 413
  4. Params & Query Validated and typed
    can stop here : 400
  5. Middlewares Authentication, context extensions
    can stop here : any status
  6. Body Validated, only once middlewares passed
    can stop here : 400
  7. Handler Gets clean, typed data

As fast as what it is built on

Mousse runs within measurement noise of raw µWebSockets.js, while adding routing, middlewares, validation and the whole Context API.

Requests per second on a plain-text route, higher is better
FrameworkComparedreq/savgp99
Mousse41 0852.16 ms5 ms
µWebSockets.js (raw)39 2772.22 ms5 ms
node:http (raw)36 5762.23 ms5 ms
koa31 3522.68 ms5 ms
fastify30 2862.72 ms7 ms
hono26 0953.35 ms8 ms
express19 8844.50 ms9 ms

100 connections for 10 seconds with autocannon, Node 24, same machine for all. Single runs : numbers vary with hardware, and differences under about 5% are not significant. Run pnpm bench to reproduce on yours.

And the rest

Before you install

µWebSockets.js ships prebuilt binaries only. Mousse runs on Node.js 22, 24 and 26, on glibc Linux (x64, arm64), macOS and Windows. Alpine (musl) and odd Node versions fail at import : use an image such as node:24-slim. µWebSockets.js is installed from GitHub rather than npm, so strict package managers may need to allow it.