Typescript

Mousse is fully typed. Two generic slots drive the typing of a route: the context types (what flows through the request/response) and the context extension (extra members middlewares attach to the context).

app.METHOD<ContextTypes, ContextExtension>(pattern, ...handlers);

Context Types

ContextTypes describes the shapes bound to a route:

type ContextTypes = {
	Body?: any;       // Type returned by c.body()
	Response?: any;   // Type accepted by c.json() and handler returns
	Query?: any;      // Type of c.query
	Params?: string;  // Union of the route parameter names
}

When a route declares schemas, all of this is inferred automatically — explicit generics are only needed on schema-less routes:

type CreateUser = {
	Body: { name: string; email: string };
	Response: { id: number };
	Params: 'org';
};

app.post<CreateUser>('/orgs/:org/users', async (c) => {
	const body = await c.body();   // { name: string; email: string }
	c.param('org');                // OK
	c.param('nope');               // Type error
	return { id: 1 };              // Must match Response
});

Handlers are also constrained per method: for example c.body is not available on get/head/options handlers, and only sustainable methods expose c.sustain().

Extending the Context

Middlewares often attach data to the context (a user, a session...). Declare the extension in the second generic slot and every function of the route sees it:

type Auth = {
	user: { id: number; name: string };
};

app.get<any, Auth>('/me',
	(c) => {
		c.user = lookupUser(c.getHeader('authorization'));
	},
	(c) => {
		return c.user; // typed, no cast needed
	});

An extension can also be declared router-wide, so every route registered on it is typed with the extension by default:

const router = new Router<any, Auth>();

router.get('/profile', (c) => c.user);

Note: use() currently returns the router unchanged (this) for chaining; it does not propagate a new extension type to the returned value. Declare extensions on the router generics or per-route.