Middleware
Compose behaviour around your routes: globally, or right next to the route it protects.
A middleware receives the request, the response helper and a next function. Return next() to continue, or return a Response to stop the chain.
import type { SpongMiddleware, SpongRequest } from 'riser-bob' const logger: SpongMiddleware<SpongRequest> = async ( spongReq, spongRes, spongNext) => { console.log(spongReq.method, spongReq.path) return spongNext()}| Argument | Type | Description |
|---|---|---|
spongReq | SpongRequest | The request, shared along the chain. |
spongRes | SpongResponse | The response helper, so a middleware can answer with SpongUnauthorized() and friends. |
spongNext | () => Promise<Response> | Runs the rest of the chain and resolves with its Response. |
Global middleware
bob.spongUse(loggerMiddleware)Route-specific middleware
Pass middleware after the handler. It runs only for that route.
bob.spongGet('/admin', handler, adminOnly)You can pass several. They run in the order you list them.
bob.spongDelete('/api/users/:id', handler, auth.middleware, admin.middleware)Execution order
- Global middleware runs first, in the order you registered it.
- Route-specific middleware runs next, in the order you passed it.
- The handler runs last.
- Code placed after
await spongNext()runs on the way back, in reverse order.
const timing: SpongMiddleware<SpongRequest> = async ( spongReq, spongRes, spongNext) => { const start = performance.now() const response = await spongNext() // everything after this runs on the way back console.log(spongReq.path, performance.now() - start, 'ms') return response}Stopping the chain
Return a Response instead of calling spongNext() and nothing further runs.
const maintenance: SpongMiddleware<SpongRequest> = async () => { return new Response('Back soon', { status: 503 })}A class-based middleware
When a middleware needs configuration or helpers, write it as a class with a middleware property. This is how AuthMiddleware is built, and it fits the module layout. The example below allows only users with the admin role. Put it after the auth middleware, because it reads spongReq.user.
import type { SpongRequest, SpongResponse } from "riser-bob"; export class AdminMiddleware { readonly middleware = async ( spongReq: SpongRequest, spongRes: SpongResponse, spongNext: () => Promise<Response>, ): Promise<Response> => { if (!spongReq.user) { return spongRes.SpongUnauthorized({ success: false, message: "Authentication required", }); } if (spongReq.user.role !== "admin") { return spongRes.SpongForbidden({ success: false, message: "Admin access required", }); } return spongNext(); };}this.bob.spongDelete( "/api/users/:id", this.controller.delete, this.authMiddleware.middleware, // sets spongReq.user this.adminMiddleware.middleware, // reads spongReq.user);MiddlewarePipeline
Routes build their own pipeline from your middleware. If you need to compose middleware by hand, MiddlewarePipeline is available. execute takes the request, the response helper and the final handler that runs when every middleware called next.
import { MiddlewarePipeline } from 'riser-bob' const pipeline = new MiddlewarePipeline<SpongRequest>()pipeline.use(logger).use(timing) const response = await pipeline.execute(spongReq, spongRes, async () => { return new Response('done')})