CORE CONCEPTS
Built-in Middleware
Futon ships a small set of production-ready middleware, all exported from @rasenganjs/futon.
cors()
Handles preflight OPTIONS requests and decorates responses with CORS headers.
import { cors } from '@rasenganjs/futon'; app.use( cors({ origin: 'https://myapp.com', credentials: true, }) );
logger()
Logs method, path, status, duration, and response size for every request.
import { logger } from '@rasenganjs/futon'; app.use(logger()); // default, colorized single-line app.use(logger({ skip: ['/health'] })); // skip noisy paths app.use(logger({ log: (entry) => pino.info(entry) })); // structured logging
log receives a structured LogEntry ({ method, pathname, search, status, duration, size }) instead of a pre-formatted string, so you control the output format entirely.
bodyParser()
Eagerly parses the request body based on Content-Type and stores the result on ctx.state (default key "body") and ctx.body.
import { bodyParser } from '@rasenganjs/futon'; app.use(bodyParser()); app.post('/api/data', async (ctx) => { return json({ received: ctx.body }); });
GET, HEAD, and DELETE requests are never parsed.
basicAuth() and bearerToken()
Extract credentials from the Authorization header. Both accept an optional verify callback — without it, credentials are decoded/extracted but not validated, leaving that decision to your handler.
import { basicAuth } from '@rasenganjs/futon'; app.use( basicAuth({ verify: (username, password) => username === 'admin' && password === 'secret', }) ); app.get('/protected', async (ctx) => { const { username } = ctx.get('auth'); return json({ user: username }); });
import { bearerToken } from '@rasenganjs/futon'; app.use( bearerToken({ verify: async (token) => (await db.users.findByToken(token)) ?? false, }) ); app.get('/me', async (ctx) => json({ user: ctx.get('token') }));
With verify provided, a missing/invalid credential short-circuits with 401 Unauthorized (plus the matching WWW-Authenticate header) before your route handler ever runs.
compress()
Gzip/Brotli/deflate-compresses response bodies over a size threshold, based on Accept-Encoding.
import { compress } from '@rasenganjs/futon'; app.use(compress()); // compresses bodies over 1 KB by default
Uses the Web CompressionStream API — available in modern Node (22+), Bun, Deno, and browsers. Skips compression silently where unavailable.
requestId()
Assigns a unique ID to every request — reads X-Request-Id if the client sent one, otherwise generates a UUID.
import { requestId } from '@rasenganjs/futon'; app.use(requestId()); app.use(async (ctx, next) => { console.log('Handling request', ctx.get('requestId')); return next(); });
bodyLimit()
Enforces a maximum request body size, rejecting with 413 Payload Too Large before the body is fully buffered:
import { bodyLimit } from '@rasenganjs/futon'; app.use(bodyLimit({ maxSize: 5 * 1024 * 1024 })); // 5 MB
It checks Content-Length first, then streams and counts bytes as a fallback — so a request lying about its size is still caught mid-stream.
