Middleware
Tool
A layer every request passes through on its way in and every response passes through on its way out, handling the checks and records that every feature needs.
Why it exists
Features do different things, but what has to be confirmed before a feature starts is mostly the same. Creating a document, leaving a comment, exporting a file — each one opens with the same questions. Is the person who sent this request signed in? Are they allowed to touch this document? Did anything record who called what, and when?
There was a time when the code answering those questions was copied into every feature. Twenty endpoints meant twenty copies of the same check, and those twenty copies drifted apart over the months. One endpoint verified that the token had not expired; another only checked that a token was attached at all.
Incidents came out of the copy nobody remembered. The day Pagedeck changed its sign-in rules, Theo searched the codebase and updated twenty-three places — and missed an export endpoint added a month earlier. That one endpoint ran on the old rules for three months before anyone noticed. The code wasn't wrong. Having many copies of it was.
Middleware exists to collapse those copies into one. It inserts a layer between the door where requests arrive and the place where features actually do their work, and it routes every request through that layer. The check happens once, in one file.
What it solves
Middleware removes the common handling that used to be scattered across features. A request arrives, passes through the layers in order, and each layer does its part and hands it on. A request that fails a layer turns around there, and the feature code never runs at all.
Laid out in order, the layers on the Pagedeck server look like this.
- Recording — writes down which request arrived at which address. Logging is the practice of keeping a record of what happened during execution so it can be read later.
- Authentication — confirms who sent the request. It checks whether the attached token is one the system issued and whether it is still valid. If not, the request is rejected right there.
- Authorization — confirms that this person is allowed to do this particular thing. Being signed in doesn't let someone delete another team's document.
- Rate limiting — blocks a caller who sends far too many requests in a short window. Pagedeck caps its application programming interface (API), the channel programs use to talk to each other, at 600 calls a minute.
- Shape checking — confirms that the values the request should carry are present, and that letters haven't arrived where a number belongs.
Only after those five layers does the business logic run. Business logic is where company rules live in code — "a free plan gets three documents" and the like. Because middleware handles the front, the code here can assume the request has already been filtered and get on with its own job.
Responses pass back through the same layers in reverse, which is where anything that has to be true of every response gets added. Measuring how many milliseconds the work took and attaching that number to the record happens on the way out, and so does stamping each response with the identifier that lets a support ticket be traced back to a single line in the log.
Alternatives
Common handling doesn't have to live in middleware. There are three places to put it, and what separates them is whether the check applies to every request.
- Calling it inside each feature — the first line of the feature calls the check function directly. What each feature verifies is visible right where it sits. With one or two checks and fewer than ten endpoints, this reads more easily. At a hundred endpoints the problem described above comes straight back.
- Middleware — one layer at the door, everything passes through. Fix one copy and every endpoint gets it. The trade is that reading the feature code tells you nothing about what runs in front of it. Chasing a bug without knowing where the check happens is a new engineer's first wall, and the order the layers are registered in becomes a piece of behavior that no single feature file describes.
- In front of the server — blocking the request before it reaches the server at all. Work that can be judged without knowing your service's rules, like rate limiting or bot filtering, goes to something like an API gateway, the layer that gathers several services behind a single entrance. A blocked request then costs no server resources.
Real systems mix all three. Anything that applies to every request goes in middleware, a check that only one feature needs stays inside that feature, and defenses unrelated to your rules sit out front. One question sorts most cases: if someone adds a new endpoint and forgets this check, does an incident follow? If so, it belongs in middleware.
How it sounds at work
"Middleware already filters that. You don't need to check it again in the handler."
The check finished before the request reached the feature, so don't create a second copy of it.
"Should rate limiting go in middleware or out in front?"
A proposal about where to block — inside the server or before requests get there.
"In this order, the limit runs before authentication. Can you swap them?"
Layers execute in the order they're registered, and counting calls before knowing who is calling causes problems.
At Pagedeck the phrase comes up most often in code review for a new endpoint. When Theo puts up an export endpoint, the first thing a reviewer looks at isn't the feature but which layers the endpoint sits behind. If it was mounted on the existing stack, there should be no check code in it at all; if check code does appear, the question is why this endpoint verifies something on its own.
What people get wrong
The most common mistake is assuming anything shared can be hoisted into middleware. It's convenient — one change reaches every endpoint — and adding a layer is a few lines, so it looks free. But every request goes through every layer. A request that only reads a list of documents still passes through the layer that checks billing status. If one layer reads the database one more time, that cost attaches to all traffic. Pagedeck once watched responses slow by 40 milliseconds for no visible reason; a lookup needed by three endpoints had been hoisted into a layer that the other hundred passed through too.
Treating middleware as a security guarantee is the same mistake in a different shape. The layer only sits on the path requests come in through. A background job that touches the same data after the response has gone out never passes through it, so a permission check that lives only in middleware leaves that path uncovered.
Where to go next
Last updated 2026-09-16