Middleware

Drop a _middleware.ts in any app/routes/** directory and its default export — a Hono middleware — runs before every page, handler, loader, and action at or under that directory. Call next() to continue; return a Response to short-circuit. This is the declarative place for an auth guard.

// app/routes/admin/_middleware.ts  →  guards /admin and everything under it
import type { PageMiddleware } from "chevalier";
import { getSession } from "chevalier";

const guard: PageMiddleware = async (c, next) => {
  const session = await getSession<{ userId: number }>(
    c,
    Deno.env.get("SESSION_SECRET")!,
  );
  if (!session.data.userId) return c.redirect("/login");
  await next();
};

export default guard;

A _middleware.ts guards its own directory index (/admin) and its subtree, but not siblings. Nest directories to layer guards: they compose outer-to-inner, so routes/_middleware.ts wraps routes/admin/_middleware.ts. One guard per directory — compose multiple concerns inside the handler.

Observability

Logging and metrics are bring-your-own — Chevalier ships no logger. To log every request, put a _middleware.ts at the root of app/routes. Drop in Hono's logger for a quick start, or write your own for structured lines:

// app/routes/_middleware.ts  →  logs every request
import type { PageMiddleware } from "chevalier";

const log: PageMiddleware = async (c, next) => {
  const start = performance.now();
  await next();
  const ms = (performance.now() - start).toFixed(1);
  console.log(`${c.req.method} ${c.req.path} ${c.res.status} ${ms}ms`);
};

export default log;