Aura Router

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:


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, and body.
  • 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 handler

Example

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
    },
  ],
})