HTTP middleware
Apply path-scoped HTTP policy, understand execution order, and test short-circuit and pass-through behavior.
Use HTTP middleware for policy that needs the request or response, such as CORS, cookies, or early rejection. Put it under src/routes/middleware/.
Keep business authorization that must apply to jobs, tools, and direct calls in domain functions as well.
There is no service-middleware layer or middleware option on defineService.
Scaffold middleware
From your application’s root:
bun run relkit add middleware order-policy --path /orders/\*
Creates src/routes/middleware/order-policy.middleware.ts with a pass-through next() call.
It does not implement authentication or authorization. --path is required headlessly; the
interactive prompt suggests *. Use a URL scope such as /orders/:orderId or a trailing
wildcard, not bracket-directory syntax. No service selection is needed.
For every middleware scaffolding flag, see the middleware command reference.
Define path-scoped policy
/** Checks the demonstration order authorization function before forwarding matching HTTP requests; denial returns 401. */
import { defineMiddleware } from "@relkit/app/routes";
import orders from "../../services/orders/service.js";
const orderAuth = defineMiddleware("/orders/*", async (context, next) => {
const result = await orders.authorizeOrder.invoke({
authorization: context.req.header("authorization") ?? "",
});
if (!result.allowed) return context.json({ error: "unauthorized" }, 401);
await next();
});
export default orderAuth;
The middleware reads a header, delegates the decision to a public function, and returns 401 when
denied. It calls next() only when processing should continue.
The handler receives the Hono context, next, and a RELKIT context containing signal, env,
auth, log, and time. It does not receive the route target’s validated input or its dependency clients.
Call public service functions for domain behavior rather than importing their private implementation.
Choose the match scope
| Pattern | Intended scope |
|---|---|
* |
Global middleware |
/orders |
The collection path |
/orders/:orderId |
A named path parameter |
/orders/* |
An Orders path subtree |
Middleware patterns use :parameter and a trailing *, not the route directories’ bracket syntax.
The declared pattern controls matching; nesting a middleware file under a directory does not scope it.
Use the narrowest pattern that covers the policy, and test both a subtree’s base path and its children.
Include callback and preflight requests in those tests so broad authentication does not block them accidentally.
Understand execution order
For a function-backed HTTP endpoint, processing runs through framework request setup and limits, registered authentication, matching HTTP middleware, the route’s rate limiter, request mapping, function input validation and invocation, then response mapping.
Matching declared middleware is registered in normalized ID order, not service membership or import
order. await next() uses onion-style execution: before-code runs inward, and after-code runs outward
in reverse order. Returning a Response without next() skips the downstream route.
Consequently, middleware can reject before mapping or read a header before the rate limiter, but it cannot assume function input has been validated. Avoid consuming the raw body stream in middleware when the route still needs to parse it. Use header/session policy where possible and test any body access.
Verify short-circuit and pass-through
With the commerce app running locally in test mode on its configured port 4000:
curl -i http://localhost:4000/orders/order-1
curl -i http://localhost:4000/orders/order-1 \
-H 'authorization: Bearer example'
The first request should return the middleware’s 401. The second passes this demo policy and reaches
the normal route outcome; that may be a declared not-found error if the order does not exist.
Use local development to start your own app and adjust its port.
Add integration assertions for a denied request, an accepted request, a path outside the scope,
and response headers after next(). Assert that denial prevents the target from running.
Middleware-only statuses are not automatically included in the route’s OpenAPI response contract;
document them alongside responses.
Run middleware assertions against the actual server pipeline. The current createTestApplication
HTTP helper does not execute declared HTTP middleware, so its success alone cannot verify this policy.
Include alternate entry points in policy tests
Generated clients call /rpc, not the original REST URL. The RPC runtime replays associated
path-scoped middleware, but it sees the native RPC request, not reconstructed REST path or body values.
Read Generated clients before relying on path/body-dependent policy.
Test both transports if both are reachable.