Middleware
Defining middleware
Middleware follows the three forms like everything else. A class carries its dependencies; a factory receives the container; a plain function is the bare pipe. Choose by what the middleware needs.
#The three forms
A class implements handle(event, next). A functional middleware is just that signature as a function. A factory receives the container and returns the pipe, which is how middleware gets its own dependencies.
import { Middleware, NextPipe } from '@stone-js/core'
import { IncomingHttpEvent, OutgoingHttpResponse } from '@stone-js/http-core'
@Middleware()
export class EnsureJson {
async handle (event: IncomingHttpEvent, next: NextPipe<IncomingHttpEvent, OutgoingHttpResponse>) {
if (event.get('contentType') !== 'application/json') {
throw new RuntimeError('JSON required') // short-circuit: never calls next
}
return next(event)
}
}import { defineMiddleware } from '@stone-js/core'
// Functional: the simplest form.
export const ensureJson = defineMiddleware((event, next) => {
if (event.get('contentType') !== 'application/json') throw new RuntimeError('JSON required')
return next(event)
})#Configurable middleware
Middleware that takes options is a function returning a pipe. This is exactly how the built-in guards work: requireAuth() and validate(schema) are factories that return the actual middleware.
export const rateLimit = (perMinute: number) =>
defineMiddleware((event, next) => {
if (overLimit(event, perMinute)) throw new RuntimeError('Too many requests')
return next(event)
})
// used on a route: middleware: [rateLimit(60)]#On the way out
Because a middleware wraps next, it can also transform the response after the handler runs: set a header, wrap a body, measure timing. Await next(event), then work with what it returns.
export const envelope = defineMiddleware(async (event, next) => {
const response = await next(event)
response.setContent({ data: response.getContent(), meta: { at: Date.now() } })
return response
})