awesome-everything RU
↑ Back to the climb

Backend Architecture

Middleware and DI: the two patterns that shape every backend

Crux Two patterns sit underneath every framework: middleware intercepts each request in order, and dependency injection wires services once at startup. One controls what runs around your code; the other controls what your code can reach and how you test it.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at junior altitude — the surface
◷ 11 min

Two engineers describe the same backend. One says “every request flows through auth, logging, and validation before the handler.” The other says “the handler gets a database client, a cache, and a mailer it never created.” They are describing the same code from two angles: the request flowing through layers, and the dependencies flowing into objects. Those two angles are middleware and dependency injection — and almost every framework decision you will make is one or the other.

Two orthogonal axes

Middleware and dependency injection answer different questions, and confusing them is a common source of muddled architecture:

  • Middleware is about the request axis — what runs around a handler on every request, in what order. It is dynamic, per-request, and ordered: auth, logging, body parsing, rate limiting. The previous unit showed why that order is a security boundary.
  • Dependency injection is about the wiring axis — what objects a piece of code is given instead of constructing itself. It is mostly static, decided at startup, and about coupling and testability: a handler receives a UserRepository rather than calling new UserRepository() inside itself.

One is a pipeline; the other is a graph. A request travels the pipeline; the graph is assembled once before any request arrives.

Why middleware exists: cross-cutting concerns

Auth, logging, tracing, compression, and error handling are needed by almost every endpoint but belong to none of them. Without middleware, each handler re-implements them — inconsistently. Middleware factors these cross-cutting concerns into ordered layers that wrap the handler, so they are written once and applied uniformly.

Why DI exists: inversion of control

When a class calls new EmailService() internally, it is welded to that exact implementation: you cannot swap it for a fake in a test, point it at a different config, or reuse the class elsewhere. Inversion of control flips this — the class declares what it needs (an emailer) and something external decides which concrete one it gets. That external place is the composition root, the single spot near startup that knows the real implementations and wires the object graph.

MiddlewareDependency injection
AxisThe request (what runs around code)The wiring (what code is given)
WhenPer request, dynamicallyOnce at startup, statically
ShapeOrdered pipelineObject graph
SolvesCross-cutting concernsCoupling, testability
Gets wrongOrdering → security/perf bugsHidden new → untestable code

They meet in the framework

Real frameworks fuse the two. NestJS controllers are resolved from a DI container and sit behind a middleware/guard/interceptor pipeline. Express keeps DI implicit (you wire by hand or req-attach), but the middleware model is explicit. Recognizing which axis a problem lives on is the senior reflex: “this endpoint is slow on every request” is usually middleware; “this class is impossible to test” is usually DI.

Quiz

A service class calls `new StripeClient()` directly in its constructor, and the team finds it impossible to unit-test without hitting the real Stripe API. Which axis is the problem on?

Quiz

Which best describes the difference in *when* middleware and DI act?

Order the steps

Order these events from process start to a served request:

  1. 1 Composition root wires the object graph (repositories, clients, services)
  2. 2 Framework registers the middleware pipeline in order
  3. 3 Server starts listening for connections
  4. 4 A request arrives and flows through the middleware pipeline
  5. 5 The handler runs using the dependencies it was injected
  6. 6 The response is produced and returned
Recall before you leave
  1. 01
    What is the difference between the request axis and the wiring axis, and why keep them separate?
  2. 02
    Why does middleware exist, and what problem do cross-cutting concerns create without it?
  3. 03
    What is inversion of control and what role does the composition root play?
Recap

Underneath every backend framework sit two orthogonal patterns. Middleware is the request axis: ordered layers that wrap the handler and factor cross-cutting concerns — auth, logging, body parsing, rate limiting — so they are written once and applied uniformly, with their order acting as a security and performance boundary. Dependency injection is the wiring axis: instead of a class constructing its own collaborators with new, it declares what it needs and a composition root assembles the object graph once at startup, which is what makes code swappable and testable. One is a per-request pipeline; the other is a startup-time graph. Frameworks fuse them — a NestJS controller is DI-resolved and sits behind a guard/interceptor pipeline — but the senior reflex is to ask which axis a problem lives on. The next lessons go deep on each: first the anatomy of writing correct middleware, then the mechanics of inversion of control and DI scopes.

Connected lessons
appears again in185
Continue the climb ↑Writing middleware: signatures, next(), and the three framework models
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.