Implementing Wrappers and Middleware in Clojure

Learn how wrappers and middleware add cross-cutting concerns around Clojure handlers and functions without tangling the main business path.

Middleware: A wrapper layer that intercepts a request, handler, or response path to add cross-cutting behavior such as logging, authentication, or metrics.

Wrappers and middleware are close cousins of decorator, but they deserve their own lesson because they are one of the most common real-world structural patterns in Clojure applications. In web systems especially, the middleware pipeline is often where authentication, correlation IDs, content negotiation, sessions, and metrics are attached.

Middleware Works Because Handlers Are Just Functions

In Ring-style systems, a handler is a function from request to response. That makes middleware easy to express:

1(defn wrap-request-id [handler]
2  (fn [request]
3    (let [request-id (or (get-in request [:headers "x-request-id"])
4                         (str (random-uuid)))
5          request' (assoc request :request/id request-id)
6          response (handler request')]
7      (assoc-in response [:headers "x-request-id"] request-id))))

This wrapper does one thing: ensure request and response correlation. The core handler remains focused on application behavior.

Wrappers Are Useful Outside HTTP Too

The same structural idea works around:

  • database operations
  • queue consumers
  • scheduled jobs
  • CLI commands
  • domain service functions

The pattern is broader than Ring. Ring just makes it very visible.

The Pipeline Needs a Deliberate Order

Order matters because each wrapper changes what later layers see.

Common examples:

  • auth before domain handling
  • request normalization before validation
  • exception-to-response translation outside most business layers
  • metrics and tracing around the full handler

Bad ordering can make debugging difficult or hide useful context.

Keep Middleware Focused

Strong middleware typically has one clear concern:

  • attach a request ID
  • enforce authentication
  • parse a body
  • record timing
  • map exceptions to responses

Once one wrapper starts doing several unrelated tasks, the pipeline becomes harder to understand and reuse.

Middleware Should Not Own the Domain

It is tempting to push more and more application logic into wrappers because they run “before everything else.” Resist that. Middleware is the right place for transport and cross-cutting behavior, not for core domain workflows.

If a rule depends on business meaning rather than generic request handling, it usually belongs deeper than the middleware stack.

Wrapper Stacks Need Local Explanations

Even when each wrapper is small, the assembled stack can become opaque. A little local documentation near the composition point helps a lot:

  • which wrapper adds identity
  • which wrapper adds request IDs
  • which wrapper maps errors
  • which wrapper expects parsed parameters

That makes debugging and refactoring safer because the contract is no longer implicit.

Common Failure Modes

Opaque Middleware Stacks

If the team cannot explain the order or purpose of the wrappers, production debugging will suffer.

Hidden Response Shape Changes

Middleware that silently rewrites successful results, error forms, or headers in surprising ways makes the system harder to reason about.

Cross-Cutting Layers Owning Business Policy

When middleware becomes the place where domain decisions live, the architecture starts to blur.

Practical Heuristics

Use middleware and wrappers for transport and cross-cutting concerns that genuinely belong around the main handler. Keep each layer small, name the stack clearly, and make the order intentional. In Clojure, good middleware makes the core handler simpler rather than more magical.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026