Skip to content
RELKIT
Esc
↑↓navigate↵open⌘Jpreview
On this page

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.

Reference and source