Recommended reading: I benchmarked five Node.js routers — the router benchmark that started this series.
After the router benchmark article, the obvious next topic should have been frameworks.
But the thing that kept bothering me for years was smaller, and somehow worse: request context.
Not routing. Not controllers. Not decorators.
req.
Or more precisely: what backend frameworks keep doing to req and res.
Three things always felt wrong:
- app state kept leaking into
req - middleware kept doing work nobody asked for yet
- the same request-scoped value kept getting recomputed across different layers
That is the problem Wooks event core was built to fix.
Where is the request state?
What always felt wrong in Express and, to a large extent, Fastify, was this pattern:
- middleware computes something
- sticks it onto
req - the next layer just hopes it is there
At first it is one property:
req.user = user
Then it becomes:
req.user = user
req.tenant = tenant
req.pagination = pagination
req.filters = filters
req.requestId = requestId
Now req is not really a request object anymore. It is an application context backpack.
Getting the right type for req was never as trivial as it should be. And every piece of middleware that needed to pass something downstream kept turning into "just add one more property."
That works, until it does not. Typing becomes awkward. Ownership becomes unclear. And every new piece of request-scoped state fights for a slot on an object that was never meant to carry the whole app.
Never precompute what nobody asked for
The second thing that clicked came from Vue 3.
I loved the composables model and kept thinking: backend frameworks should work like this too.
Not middleware that eagerly parses the body for every matching route. Just:
const { parseBody } = useBody()
const body = await parseBody()
And if the handler never asks for the body, the body never gets parsed.
The same applies to everything else: useCookies(), useAuthorization(), useUrlParams(), useCurrentUser().
Not "prepare everything up front, just in case." More like "resolve only what the current code actually needs."
That felt much closer to how request handling should work.
Invoke lazily, cache once
The third piece came a bit later.
In real request handling, code often asks for the same derived value more than once:
- an auth guard needs the current user, then the handler needs it too, then a logger wants it again
- one layer parses the body, another layer needs the parsed body too
That work should not run repeatedly. It should be cached for the lifetime of the current request.
Sounds obvious once you say it out loud, but most frameworks do not make it a natural default. Request-scoped computation should be lazy, shared, and cached — by design, not by discipline.
That is how Wooks was born
Wooks came from combining those three ideas — not from "let me build another HTTP framework."
- Stop treating
reqas a junk drawer for app state - Use composables to access request data only when needed
- Cache request-scoped computation so all layers share it
The result is Wooks event core:
- every event gets a context
- composables read from that context
- repeated work is shared within the event lifetime
Instead of mutating req, the event context becomes the primitive.
What this looks like in practice
A typical HTTP route:
app.get('/projects', async () => {
const requestId = useEventId().getId()
const user = await useCurrentUser().require()
const tenant = await useTenant().current()
const filters = useProjectFilters().value()
const pagination = usePagination().value()
return listProjects({
requestId,
user,
tenant,
filters,
pagination,
})
})
The point is not that the handler is shorter. The point is:
- no middleware randomly adding fields to
req - nothing eagerly parsed "just in case"
- every dependency visible where it is used
- repeated computations shared automatically within the same event
No junk-drawer req. No eager middleware work. No repeated computation.
This is bigger than HTTP
What I wanted was not "better middleware." It was a better primitive for request-scoped work.
Once context is treated as a real system, HTTP is just one place where it becomes useful. The same idea extends to CLI commands, workflows, WebSocket events, and custom event types.
That is why I think of Wooks as an event core, not "some nicer Express wrapper."
Next up: composables in practice
The next article digs into the part that made this click for me — why backend composables are not just a cute API idea, but a genuinely better primitive than middleware for request-scoped logic.
Read next: Backend composables — What happens when you replace middleware with composables for request-scoped logic.



Top comments (0)