CORE CONCEPTS
Middleware
Rasengan Server uses Futon's Middleware type unchanged ((ctx, next) => Promise<Response>) but layers it at four levels, always applied in the same order.
The Four Layers
Global → Module → Controller → Route → Validation → Handler
bootstrap((app) => { app.use(requestId()); app.enableCors({ origin: 'https://myapp.com' }); });
Two middleware are pre-registered on every ServerApp before your own app.use() calls: a request logger and a body parser (storing the parsed body on ctx.state.body).
export default defineModule({ prefix: '/admin', middlewares: [requireAdmin], controllers: [AdminController], });
export class AdminController extends Controller { middlewares = [auditLog]; // ... }
routes(router: Router) { router.get("/danger-zone", rateLimited, this.dangerZone); }
Why Order Matters
Each layer wraps the next, onion-style — a global middleware's next() call eventually reaches the module middleware, then the controller middleware, then the route middleware, then (if the route has a schema) validation, and finally the handler. A global middleware can inspect or short-circuit everything downstream; a route-level middleware only wraps that one handler.
app.use(async (ctx, next) => { const start = Date.now(); const response = await next(); // runs module → controller → route → validation → handler console.log( `${ctx.request.method} ${ctx.request.url} — ${Date.now() - start}ms` ); return response; });
CORS
app.enableCors(options?) is a thin wrapper over Futon's cors() — calling it with no arguments enables CORS with Futon's defaults:
app.enableCors(); // permissive defaults app.enableCors({ origin: 'https://myapp.com', credentials: true });
Reusing Futon's Built-ins
Anything exported from @rasenganjs/futon — logger(), bearerToken(), basicAuth(), compress(), requestId(), bodyLimit() — works unchanged with app.use() or as module/controller/route-level middleware, since Rasengan Server's Middleware type is Futon's. See Built-in Middleware for the full list.
