Guides

Rouzer Integration

Rouzer re-exports alien-middleware as its middleware provider; this page keeps the import boundary, host mapping, and shared context rules explicit.

Rouzer's root API includes alien-middleware exports such as chain, toFetchHandler, RequestContext, and RequestHandler. Rouzer routers are middleware chains too, so route handlers and middleware can share one typed request context.

Stay on the Rouzer Import Boundary

Import middleware helpers from rouzer when Rouzer owns your router, handler, or client types.

import {
  chain,
  createRouter,
  toFetchHandler,
  type Middleware,
  type RequestContext,
  type RequestHandler,
  type RouteRequestHandlerMap,
} from 'rouzer'
import type { HttpRouteTree } from 'rouzer/http'

This keeps route handlers, middleware chains, and adapter helpers using one public type surface. Use direct alien-middleware imports when your application owns the chain directly and does not need Rouzer-specific router types.

For larger application-stack patterns that are not Rouzer-specific, use Middleware Stacks.

Type Host Data Once

Create the top-level chain or router with the environment and runtime types that the adapter will provide.

type WorkerEnv = {
  DATABASE_URL?: string
  SESSION_SECRET?: string
}

const commonMiddleware = chain<WorkerEnv, {}, ExecutionContext>()

Then map the host's request arguments into host at the adapter boundary.

function createWorkerFetchHandler(handler: RequestHandler) {
  return (request: Request, env: WorkerEnv, ctx: ExecutionContext) => {
    const fetch = toFetchHandler(handler, {
      host: {
        env(name) {
          return env[name as keyof WorkerEnv]
        },
        runtime: ctx,
        waitUntil(promise) {
          ctx.waitUntil(promise)
        },
      },
    })

    return fetch(request)
  }
}

Middleware can then read environment values with context.env(name), runtime data with context.host.runtime, and background work support with context.waitUntil().

Compose Rouzer Middleware Before Routes

Put shared resources in a common chain before Rouzer route registration.

declare const dbMiddleware: Middleware
declare const i18nMiddleware: Middleware
declare const authMiddleware: Middleware
declare const apiRoutes: HttpRouteTree
declare const handlers: RouteRequestHandlerMap<typeof apiRoutes>

const commonMiddleware = chain<WorkerEnv, {}, ExecutionContext>()
  .use(dbMiddleware)
  .use(i18nMiddleware)
  .use(authMiddleware)

const apiRouter = createRouter<WorkerEnv, {}, ExecutionContext>({
  basePath: 'api/',
})
  .use(commonMiddleware)
  .use(apiRoutes, handlers)

Order the chain by dependency. If authMiddleware expects context.db, add dbMiddleware first. If a feature middleware owns routes and context together, make that feature router part of the chain before the final route tree.

Use Lazy Plugin Getters

For expensive request resources, return getters from middleware instead of opening resources eagerly.

type DbContext = {
  db: WorkerDb
}

type WorkerDb = {
  execute(sql: string): Promise<unknown>
}

declare function getOrCreateDb(databaseUrl: string): WorkerDb

const dbMiddleware = chain(
  (context: RequestContext<WorkerEnv, {}, ExecutionContext>) => {
    const databaseUrl = context.env('DATABASE_URL')

    return {
      get db(): WorkerDb {
        if (!databaseUrl) {
          throw new Error('DATABASE_URL is required to access context.db')
        }

        return getOrCreateDb(databaseUrl)
      },
    } satisfies DbContext
  }
)

The getter is copied as a property descriptor, so downstream middleware reads context.db like a normal property. This pattern keeps startup cheap and avoids opening a database connection for requests that never need it.

Use closure variables for per-request memoization.

type Session = {
  userId: string
}

declare function readSession(request: Request): Promise<Session | null>

const sessionMiddleware = chain().use(context => {
  let session: Promise<Session | null> | undefined

  return {
    session() {
      return (session ??= readSession(context.request))
    },
  }
})

Keep Adapter Error Boundaries Outside the Chain

Use the adapter wrapper for unexpected exceptions that should be reported and converted into a generic 500 response.

declare function reportException(error: unknown, request: Request): void

function createSafeFetch(handler: RequestHandler) {
  const fetch = toFetchHandler(handler)

  return async (request: Request) => {
    try {
      return await fetch(request)
    } catch (error) {
      reportException(error, request)
      return Response.json(
        { message: 'Internal server error' },
        { status: 500 }
      )
    }
  }
}

Inside middleware and route handlers, return a Response for expected request outcomes such as authentication failures, validation errors, or feature routes that deliberately handle one path.

Share Host Access with Non-Request Work

If scheduled jobs or other host hooks need the same environment values as middleware, extract small helpers that accept an env reader instead of requiring a full request context.

type EnvReader<TEnv extends object> = {
  env<TName extends keyof TEnv>(name: TName): TEnv[TName]
}

function getDatabaseUrl(context: EnvReader<WorkerEnv>) {
  return context.env('DATABASE_URL')
}

Request middleware can pass the full request context. Scheduled jobs can pass a small object backed by the host's environment.

Rouzer may wear the space helmet, but alien-middleware is still the tiny pilot working the controls.