CodeOath
← All posts
Architecture & Patterns50 min total · 13 parts

Middleware Pipelines Compared: ASP.NET Core, Express.js, and Django

Part 2 of 13 · ~2 min

The Shape Under the Syntax

Diagram of a request passing down through middleware layers, then a response passing back up

Strip away all three frameworks' syntax and what's left is one shape, and it's worth holding it in your head before any of the framework-specific code shows up, because every single thing that follows is a variation on it.

A request for POST /returns/4471/approve enters at the top of a list of functions and moves down it, one at a time. Each function gets exactly two choices:

  1. Do its job, then pass the request along — log it, check something, attach some data to it — and hand off to whatever comes next in the list.
  2. Stop the request right there and produce a response itself — reject it, serve it from a cache, answer it directly — without ever reaching the functions below it, Marcus's route handler included.

Something eventually produces a response — usually the route handler that actually approves the return — and once that happens, the response travels back up through every layer the request passed through, in the exact reverse order. That's the detail that makes this whole thing worth drawing as an onion rather than a straight line: each layer wraps everything beneath it, gets a turn on the way in, and gets a second turn on the way out, and those two turns are usually two different lines of the same function.

Why Middleware Exists At All

Every one of Counter's three builds needs the same handful of things applied to most requests: who's making this request, are they allowed to approve a return this large, log it either way, and — for the two requests a second that hit the mobile-dashboard SPA at a different origin — answer the browser's CORS preflight correctly. Bolt all of that onto the front of approveReturn directly and you'd be retyping the identical five or six lines into every handler that happens to need them, with no guarantee any two copies stay in sync six months later. Middleware sidesteps that entirely: each concern gets written once, ordered however the team decides, and reused by every route that needs it — none of it living inside the handler function itself.