Files
skills/docs/api-stack/api/api-router/src/middleware/middleware.md
2026-05-29 08:20:56 +01:00

3.2 KiB

middleware

(no submodules)

Purpose

The middleware module defines the onion-model Middleware contract and the composeMiddleware builder. Every adapter consumes the same shape so middleware authored against the abstract contract drops into every router implementation (Hono, in-memory, the worker-bridge router) unchanged.

The contract is intentionally narrow: a middleware receives the request and a next() continuation, and must return a response. There is no ctx mutation handle, no implicit state — middleware threads explicit values via wrapping responses and inspecting the request shape.

Features

  • Middleware<ReqBody, ResBody> — type generic over the request body and response body shapes so handler-level types flow through.
  • composeMiddleware([…]) — builds a single composed middleware from a list; order is preserved.
  • next() enforcement: a middleware that calls next() twice in a row has its dispatch rejected. (Multiple sequential next() calls across different middlewares are correct; only same-middleware repeats fail.)
  • Synchronous return values are accepted (wrapped in Promise.resolve).

Usage: Basic

import { composeMiddleware } from "@suaveplan/api-router/middleware";

const log = composeMiddleware([
    async (req, next) => {
        const res = await next();
        return res; // pass-through with hook points
    },
]);

Usage: Advanced

import { composeMiddleware, type Middleware } from "@suaveplan/api-router/middleware";

const auth: Middleware = async (req, next) => {
    if (req.headers.get("authorization") === null) {
        return { status: 401, headers: {}, body: { code: "UNAUTH" }, streaming: false };
    }
    return next();
};

const trace: Middleware = async (req, next) => {
    req.span.setAttribute("path", req.url);
    const res = await next();
    req.span.setAttribute("status", res.status);
    return res;
};

const stack = composeMiddleware([trace, auth]);
// Execution: trace → auth → terminal → auth → trace

API Reference

  • Middleware<ReqBody, ResBody> — (req, next) => RouterResponse | Promise<RouterResponse>.
  • composeMiddleware(list) — returns a single composed Middleware.
  • PipelineStep — literal union of every per-request pipeline step name.
  • PLUGIN_INSERTION_STEP — the constant "post-validation"; the single insertion point plugin middleware is allowed.
  • assertPluginInsertionStep(step) — throws ROUTER.MIDDLEWARE_INSERTION_DENIED if step isn't the allowed boundary.

Implementation Notes

composeMiddleware uses the textbook indexed-dispatch pattern: each call to next() advances an internal index, and double-next() from the same middleware is detected by comparing the new index to the recorded high-watermark. The detection rejects the inner Promise rather than throwing synchronously so the error propagates through the caller's existing .catch / try/await machinery.

Synchronous middlewares are supported — the return value is wrapped in Promise.resolve so callers don't have to choose between async and plain functions. The terminal continuation is invoked with no arguments and must produce the response; adapters wire this to the handler call.