How to write middleware in Base

The work around your handlers, split by timing: global before the handler is known, handler after.

Building APIs
AhraJuly 22, 2026

How to write middleware in Base

Middleware is code that runs around your request handling: before a handler, sometimes instead of it, to do the cross-cutting work that does not belong in any single route. Authentication, logging, adding a header, reading a token off the request. Base gives you two kinds, and the difference between them is worth understanding, because it is the difference between running before the handler is known and running once it is.

The shape of a middleware

A middleware in Base receives the request context and decides what happens next. It has three moves:

  • Return nothing to continue to the next middleware and, eventually, the handler.
  • Return a Response to short-circuit: the pipeline stops and your response is sent. This is how a middleware blocks a request, like rejecting an unauthenticated call.
  • Throw an error to signal a failure, which Base turns into an error response.

You can write a middleware as a plain function or as a class. The function form is simplest. Reach for the class form when your middleware needs dependencies injected, because a class participates in dependency injection like the rest of Base.

Global middleware: runs before the handler is resolved

Global middleware runs on every request, early, before Base has figured out which handler the request is for. It receives the request context and is the right place for work that does not depend on knowing the handler: reading a header into the context, logging that a request arrived, enforcing a check that applies to everything.

Because it runs before handler resolution, global middleware cannot ask "which handler is about to run?" It runs ahead of that answer. That is the trade: it sees every request, but it sees them before they have been routed.

Handler middleware: runs once the handler is known

Handler middleware runs later in the pipeline, after the dispatcher has resolved which handler will serve the request. By the time it runs, the handler is guaranteed to be present on the context. That makes it the right place for work that depends on the specific handler: inspecting what was matched, acting on metadata the handler declared, doing per-route work that global middleware is too early to do.

Same three moves apply: continue, short-circuit with a Response, or throw. The only difference from global middleware is when it runs and, therefore, what it is allowed to assume is already known.

Choosing between them

The rule is simple. If your cross-cutting work applies to every request and does not care which handler is coming, it is global middleware. If it needs to know the resolved handler, it is handler middleware. Reaching for the handler from a global middleware is the tell that you wanted the handler-scoped one.

You register both in your settings object, alongside everything else your worker declares. Global middleware and handler middleware each have their place in the middleware settings, so the framework knows which pipeline phase to run them in.

The mental model to keep

Middleware is the work around your handlers, expressed as a small function or an injectable class that can continue, stop, or fail a request. Base splits it by timing: global middleware before the handler is known, handler middleware after. Pick by what your code needs to see, and let the settings object place each in the right phase.

How to write middleware in Base • System