Middlewares
Middlewares run around your route handlers to add cross‑cutting behavior such as authentication, logging, CORS, and request/response shaping. There are two types:
- Global Middlewares: Applied to every request handled by a router.
- Endpoint Middlewares: Defined per endpoint; run with typed request context.
What you'll learn
Good to know
- Middlewares let you verify, enrich, or modify the request/response around the handler.
- Endpoint middlewares have full type inference for
params,searchParams, andbody. - Global middlewares run for all requests handled by a router and operate on the original
Request.
Type inference
Endpoint middlewares are type‑safe based on the endpoint’s Zod schemas for params, searchParams, and body. These are inferred automatically from your endpoint configuration. For details, see Zod Integration.
Global middlewares do not receive the typed context; they see the original Request.
Middlewares
Endpoint Middlewares
Use createEndpointConfig to declaratively attach middlewares and schemas to an endpoint.
Basic Usage
In the below example shows the basic usage of the middlewares and it doesn't provide usage of request context.
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"
const config = createEndpointConfig({
middlewares: [
async (ctx) => {
console.log(`Request to ${ctx.url}`)
return ctx
},
],
})
const handler = async () => Response.json({})
const endpoint = createEndpoint("GET", "/protected", handler, config)Context Usage
The context usage offers type-inference powerred by the Zod Schemas which allows to add type-safe, validate and map the values, in the below example shows the usage of the endpoint middleware using context request.
import { z } from "zod"
import { createEndpoint, createEndpointConfig } from "@aura-stack/router"
const verifyToken = (token: string) => {
/* Add logic */
return token
}
const authConfig = createEndpointConfig({
middlewares: [
async (ctx) => {
const token = ctx.request.headers.get("authorization")
if (!token || !token.startsWith("Bearer ")) {
throw new Error("Unauthorized")
}
/*
Verify token (simplified)
*/
const userId = verifyToken(token.slice(7))
/*
Add user info to context
*/
ctx.headers.set("x-user-id", userId)
return ctx
},
],
})
const getProfile = createEndpoint(
"GET",
"/profile",
async (ctx) => {
const userId = ctx.headers.get("x-user-id")
return Response.json({ userId, name: "John" })
},
authConfig
)Global Middlewares
Applied to all endpoints in a router via createRouter config:
Middleware Chaining
Multiple middlewares execute in order:
const config = createEndpointConfig({
middlewares: [
/*
1. First middleware - logging
*/
async (ctx) => {
console.log("1. Logging request")
return ctx
},
/*
2. Second middleware - auth
*/
async (ctx) => {
console.log("2. Checking auth")
const token = ctx.request.headers.get("authorization")
if (!token) throw new Error("Unauthorized")
return ctx
},
/*
3. Third middleware - add metadata
*/
async (ctx) => {
console.log("3. Adding metadata")
ctx.headers.set("x-processed", "true")
return ctx
},
],
})Execution Order
When both global and endpoint middlewares are defined:
1. Global middlewares (from createRouter)
↓
2. Endpoint middlewares (from createEndpointConfig)
↓
3. Route handlerExample
import { createEndpoint, createEndpointConfig, createRouter } from "@aura-stack/router"
const endpointConfig = createEndpointConfig({
middlewares: [
async (ctx) => {
console.log("2. Endpoint middleware")
return ctx
},
],
})
const endpoint = createEndpoint(
"GET",
"/test",
async (ctx) => {
console.log("3. Handler")
return Response.json({ ok: true })
},
endpointConfig
)
const router = createRouter([endpoint], {
middlewares: [
async (ctx) => {
console.log("1. Global middleware")
return ctx
},
],
})