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

router.ts
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:

Route-level middleware
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():

Standalone Router
import { Futon } from '@rasenganjs/futon'; const app = new Futon(); const router = new Router(); router.get('/health', async () => json({ ok: true })); app.use(router.middleware());

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.

Quick Start
Route Patterns