Import
ts
import { defineMiddleware } from "@noverax/courier/middleware"Signature
defineMiddleware takes the middleware's name, its phase and a handler of that phase, and returns a Middleware. List it in a source's middleware, a base's courier({ middleware }), or one endpoint's .use():
ts
import type { AfterHandler, BeforeHandler, Middleware, WrapHandler } from "@noverax/courier/middleware"
declare function defineMiddleware(name: string, phase: "before", handler: BeforeHandler): Middleware
declare function defineMiddleware(name: string, phase: "after", handler: AfterHandler): Middleware
declare function defineMiddleware(name: string, phase: "wrap", handler: WrapHandler): MiddlewareParameters
name
A string: what Studio calls the middleware. Its Middleware view, each request's pipeline and its timings show it under this name.
phase
When the handler runs, and so what it is handed.
ts
type MiddlewarePhase = "before" | "after" | "wrap"before— changes the request — its headers, its variables — and lets it go on; it never sees the answerafter— receives the answer and returns it, changed or notwrap— callsnext()once, more than once or not at all, and returns an answer; for timing, retries, short-circuiting and error handling
handler
The function for that phase, handed the call's context:
ts
type BeforeHandler = (ctx: MiddlewareContext) => void | Promise<void>
type AfterHandler = (
ctx: MiddlewareContext,
response: MiddlewareResponse,
) => MiddlewareResponse | Promise<MiddlewareResponse>
type WrapHandler = (
ctx: MiddlewareContext,
next: () => Promise<MiddlewareResponse>,
) => Promise<MiddlewareResponse>ctx.source,ctx.group,ctx.method— the endpoint being calledctx.variables— the call's variables, which abeforeorwraphandler may replacectx.headers— the request's headers, to read or setctx.transport—"http"for a request,"sse"or"ws"for a stream or a channelctx.kind— whether the endpoint reads or writesctx.signal— fires when the caller stops waitingctx.request— the incoming request, when a proxy runs the callresponse—{ status, data, headers }: the answer anafterhandler receives andnext()resolves to
Basic Usage
lib/middleware.ts
import { defineMiddleware } from "@noverax/courier/middleware"
export const timing = defineMiddleware("timing", "wrap", async (ctx, next) => {
const start = performance.now()
const response = await next()
const ms = Math.round(performance.now() - start)
console.log(`${ctx.source}.${ctx.group}.${ctx.method} → ${response.status} in ${ms} ms`)
return response
})Then run it for every endpoint of a source:
api/index.ts
import { defineApi } from "@noverax/courier/define"
import { timing } from "@/lib/middleware"
export const api = defineApi({
baseURL: process.env.API_URL!,
middleware: [timing],
groups: { users },
})Middleware run in the order they are listed, the first outermost: it runs first on the way in and last on the way out.
Related
- Middleware overview — the onion model and
composeMiddleware - Built-in middleware — auth, retry, cache, log, rate limit
- Custom middleware — patterns: enriching, unwrapping, short-circuiting