What Is Middleware? 7 Things Every Developer Should Know (With Examples)

November 2, 2026 · Web Development

Every web application repeats the same chores on nearly every request: log it, check the caller is allowed in, parse the body, handle errors. Copy that code into every handler and you have dozens of copies to keep in sync; forget one, and one route quietly skips authentication. What is middleware? It is the layer that removes that duplication: a chain of small functions every request passes through before reaching the handler, each doing one cross-cutting job. This guide explains that chain, the three choices every middleware function makes, and the ordering rules that decide whether it helps or hurts.

Illustration of a request passing through checkpoint gates toward a server, with the reply returning through the same gates
One request, several checkpoints, one handler: middleware runs before the handler on the way in and after it on the way out.

1. The problem middleware solves: the same work in every handler

Work that applies to many routes has a name: a cross-cutting concern. Logging is one — every request should be recorded, not just the ones you remembered to instrument. Authentication is another — every protected route needs the same token check. So are parsing bodies, adding security headers, and turning exceptions into clean errors.

Without middleware, each handler does all of this itself, and the same logic is copied across the codebase. A fix to the authentication check has to be applied in every copy, and the copies drift apart over time. With middleware, the logic lives in exactly one place and runs for every route it is registered on, leaving the handler free to answer its own request. That separation is why frameworks built around a REST API almost always offer a middleware pipeline rather than leaving the chores to handlers.

2. The pipeline: a chain with a handler at the end

Picture the request walking down a corridor of doors. Each door is a middleware function, which does its work and opens the next door, until the last door — not middleware at all, but the route handler. The handler produces the response, and the response walks back out through the same doors in reverse order, giving each middleware a second chance to act: add a header, record timing, or compress the body.

Two properties matter. First, the pipeline is ordered: middleware runs in registration order, and the response unwinds in the opposite order, like nested function calls. Second, the handler is just the last step. Frameworks agree: in Express, an application is essentially a series of middleware function calls, and in ASP.NET Core the request passes through a pipeline of components, each able to run code before and after the next one. Different frameworks, same shape.

Diagram of a chain of middleware boxes ending in a handler box, with a return arrow looping back through the chain
The pipeline: request in through each middleware, handler at the end, response back out in reverse order.

3. The three choices: pass it on, change it, or stop it

Every middleware function faces the same three options. It can pass the request on unchanged, doing its own work — logging, for example — and calling the next step. It can change the request or the response: attach the authenticated user for the handler, parse a JSON body into an object, or add a response header. Or it can end the request itself, sending a response and never calling the next step.

That third option has a name: short-circuiting. It is how authentication works — an unauthenticated request gets a 401 from the auth middleware and the handler never runs. The same mechanism serves rate limiting and maintenance mode, which answer requests before any handler runs. Short-circuiting is not a pipeline failure; it is one of its main uses. The bug version is different: a middleware that neither sends a response nor passes the request on. Express documents that case plainly — if the current middleware does not end the request-response cycle, it must call next(), otherwise the request is left hanging.

4. A verified example: logging, authentication, and a handler

The idea is easier to trust once you can run it. This Python sketch implements the pipeline directly — no framework, just a list of functions and a handler — so the mechanics are visible:

def run_pipeline(middlewares, handler, req):
    def build(i):
        if i == len(middlewares):
            return lambda r: handler(r)
        return lambda r: middlewares[i](r, build(i + 1))
    return build(0)(req)

def logger(req, nxt):
    print("logger: in")
    res = nxt(req)          # pass control down the chain
    print("logger: out")    # runs on the way back out
    return res

def auth(req, nxt):
    if req.get("token") != "secret-token":
        return (401, "Unauthorized")  # short-circuit
    req["user"] = "ada"     # change the request for the handler
    return nxt(req)

def handler(req):
    return (200, f"Hello, {req['user']}")

Run locally, the behaviour matches the model. An authenticated request returns a 200 greeting, with the logger printing on both sides of the handler — in, then out. An unauthenticated request returns 401, the handler never runs, and the logger still prints its "out" line, because it wrapped the auth step that produced the answer. All three choices are visible in a few lines: the logger passes the request on, auth changes it or stops it, and the handler answers it.

Diagram of a request stopped at a middle checkpoint gate, with the final handler box greyed out and untouched
Short-circuiting: the auth middleware answers 401 itself, so the handler never runs — and the outer logger still sees the way out.

5. Order is the whole game

Because middleware runs in registration order, that order is a design decision, not a detail. Put the logger first and it sees every request, including ones later middleware rejects. Put authentication before the handler and the handler can trust every request reaching it. Put body parsing before validation, or validation has nothing to validate. And put error handling outermost — registered first, or in Express, defined last as a four-argument function — so it catches errors from everything inside it.

Get the order wrong and the failure is silent. Authentication middleware registered after the routes it was meant to protect never runs for them, and the routes serve unauthenticated traffic while looking protected. A CORS middleware placed after authentication can reject legitimate preflight requests before the CORS logic sees them. ASP.NET Core states the rule bluntly: the order of middleware components defines the order they are invoked on requests, with the reverse order for the response, and that order is critical for security, performance, and functionality. When a middleware bug resists explanation, check the registration order first.

6. Middleware is not a gateway, a handler, or a message broker

The word middleware is overloaded, and three confusions recur. The first is with an API gateway: a gateway sits at the edge of a system, in front of whole services, routing and rate-limiting traffic between them. Middleware sits inside one application, in front of its own handlers. A gateway decides which service a request goes to; middleware prepares it for the handler.

The second confusion is with the handler itself. A handler answers one route; middleware serves many. Code that only makes sense for one endpoint belongs in that endpoint's handler, not in the pipeline, where it would run — and cost time — on every other route too. The third is message-oriented middleware, the older enterprise meaning: software moving messages between systems. That job belongs to a message broker and queue, not to a request pipeline, and the two share little beyond the name. A session lookup is a natural middleware job; moving work between services is not.

Two more distinctions keep designs honest. Middleware that checks a token is not the token itself: a JWT carries claims, and middleware verifies them, but verification logic and token format are separate concerns. And a rate limiter as middleware enforces a policy inside one application, while rate limiting at the gateway enforces it before traffic reaches the application — the same policy at two different layers, for different reasons.

Diagram of nested layers around a central core, showing entry through layers inward and exit outward in reverse
Think onion, not line: each middleware wraps the ones inside it, so the outermost sees the request first and the response last.

7. When to use middleware — and when not to

Reach for middleware for work that is genuinely shared: logging, authenticating callers, parsing bodies, enforcing CORS, applying rate limits, and converting errors into consistent responses. Done per handler, they would be copied everywhere and drift. A pipeline keeps them in one place, in one order, tested once.

Do not reach for it for work belonging to one route, business logic only one handler needs, or anything requiring the handler's answer — middleware runs before the handler, so it cannot use a result that does not exist yet. Do not put a slow dependency in middleware either: a database or network call there runs on every request, so one route's need becomes every route's latency. And do not let middleware accumulate silently: a pipeline of twenty functions, each added for a good reason once, is one nobody can reason about — review new entries, and remove ones whose reason no longer holds.

Common mistakes to avoid

  • Forgetting to call next(). The request hangs until the client times out; every path must either send a response or call next().
  • Assuming next() stops your function. Code after next() still runs, on the way back out; guard the rest with a return.
  • Registering middleware after the routes it should guard. It never runs for them, yet the protection looks present.
  • Putting error handling anywhere but outermost. Errors from middleware outside it escape entirely.
  • Sharing mutable state between requests. Middleware is reused across requests; per-request data belongs on the request object.
  • Doing heavy work in global middleware. A slow lookup there taxes every route, including the health check.

Middleware is a small idea with a large effect: a pipeline where each step does one job, in a known order, with the handler at the end. Build it that way — one concern per function, authentication before handlers, error handling outermost, deliberate short-circuits — and the chores every request needs stop being copied into every handler. References: Microsoft Learn's Understand middleware training module and its Write custom ASP.NET Core middleware guide.

Frequently asked questions

What is middleware in simple terms?
Middleware is code that runs between receiving a request and running the handler that answers it. Each piece of middleware can inspect or change the request, pass it on to the next piece, or answer it directly and stop the chain. The handler only runs if every middleware before it passes the request along.
What is the difference between middleware and a route handler?
A route handler produces the answer for one specific route. Middleware runs before (and sometimes after) handlers, across many routes, for work that is not specific to one endpoint — logging, authentication, parsing, and error handling. A handler answers one question; middleware prepares or guards the question.
What happens if middleware does not call next()?
The request stops at that middleware. If the middleware also sends a response, that response is the answer — this is short-circuiting, and it is correct behaviour for authentication failures. If it sends nothing and never calls next(), the request hangs until the client times out, which is a bug.
What is the difference between middleware and an API gateway?
An API gateway sits at the edge of your system, in front of whole services, and routes, authenticates, and rate-limits traffic between services. Middleware lives inside one application, in front of its own handlers, and handles that application's cross-cutting work. A gateway protects the boundary between services; middleware organises the inside of one service.
Is message-oriented middleware the same thing as web middleware?
No, despite the shared name. Message-oriented middleware moves messages between systems, which is the job of a message broker and queue. Web middleware processes HTTP requests inside one application. When someone says "middleware" about a web framework, they almost always mean the request pipeline described in this guide.
Does middleware run on the response too?
Yes. Code in a middleware function that runs after it calls the next step executes on the way back out, after the handler has produced its response. That is how one middleware can time a request, add a header to every response, or compress a body — it sees the request on the way in and the response on the way out.
Should business logic live in middleware?
No. Middleware is for work that applies across routes: logging, authentication, parsing, rate limiting, and error handling. Business logic — pricing a cart, approving a loan, generating a report — belongs in handlers and services, where it can be tested and changed without touching every request your application serves.
When should I not use middleware?
Do not use it for one-off work on a single route — put that in the handler. Do not use it for work that needs the answer of a specific handler, since middleware runs before the handler. And do not use it to hide a slow dependency: a middleware that calls a slow service on every request makes every route slow, not just the one that needed it.

Related articles