CORE CONCEPTS
Router
The Router class registers handlers by HTTP method and pattern, then produces a single Middleware that dispatches incoming requests to the right handler. Futon owns one internal Router. The app.get() / app.post() shortcuts you've already seen just delegate to it.
Why a Radix Tree
A naive router checks every registered route in order until one matches: O(n × k) for n routes and k path segments. Futon's RasenganTreeRouter instead walks one tree node per URL segment, so lookup time is O(k) regardless of how many routes you've registered.
HTTP Method Shortcuts
import { Router, json } from '@rasenganjs/futon'; const router = new Router(); router.get('/users', async () => json(await listUsers())); router.post('/users', async (ctx) => json(await createUser(ctx.body))); router.put('/users/:id', async (ctx) => json(await replaceUser(ctx.params.id, ctx.body)) ); router.patch('/users/:id', async (ctx) => json(await updateUser(ctx.params.id, ctx.body)) ); router.delete('/users/:id', async (ctx) => { await deleteUser(ctx.params.id); return json({ ok: true }); }); router.head('/users/:id', async (ctx) => new Response(null)); router.options('/users/:id', async () => new Response(null, { status: 204 }));
Every shortcut has the same signature: (pattern, handler), where handler is (ctx: Context) => Promise<Response>.
Route-level Middleware
Middleware registered via router.use() only applies to routes defined after the call, on that router instance:
router.use(requireAuth); // requireAuth runs before this handler: router.get('/profile', async (ctx) => json(ctx.get('user')));
Route-level middleware runs in the same onion model as app-level middleware. See Custom Middleware.
Plugging the Router into Futon
Call .middleware() once, and pass the result to app.use():
import { Futon } from '@rasenganjs/futon'; const app = new Futon(); const router = new Router(); router.get('/health', async () => json({ ok: true })); app.use(router.middleware());
.middleware() snapshots the currently-registered routes. Routes added to
that Router after calling .middleware() are not picked up by that
snapshot. Register all your routes before wiring the router into app.use().
In practice you'll rarely build a standalone Router this way. app.get(), app.post(), and app.group() already delegate to Futon's own internal router.
Automatic 405 Responses
If a path matches a registered route for a different method, the Router automatically responds with 405 Method Not Allowed and an Allow header listing the methods that do match, instead of falling through to your 404 handler.
GET /users/42 → 200
DELETE /users/42 → 405 Method Not Allowed
Allow: GET, PUT, PATCH
Debugging
router.routesCount() returns the total number of registered routes across every method, useful for sanity-checking route registration in tests or startup logs.
