Overview
/ Scope
- HTTP server lifecycle & request/response wrappers
- Typed middleware pipeline (compose pattern)
- Trie-based routing with param & wildcard support
- Plugin/adapter system (extend the framework instance explicitly)
- Custom body parser (no runtime dependencies)
Highlights
01
Framework internals from first principles — nothing hidden
02
Trie-based router: O(k) lookup, no regex on the hot path
03
Zero-dependency core — every abstraction is written by hand
04
Documentation explains the why, not just the API
/ Tracks
- Research
- Open source
The Problem
Express feels like magic until you read its source. I wanted to understand the moving parts — middleware composition, routing, request lifecycle, async error propagation — by building them from scratch instead of reading about them secondhand.
Approach
Start with the smallest possible HTTP server, then add one concept at a time: request/response wrappers, then middleware, then the router, then plugins. Each layer gets its own tests and a doc page before moving on. The constraint is that every abstraction has to be explainable in a few lines — if it can't be, the design is wrong.
Key Decisions
- 01
Zero-dependency core
No Express, no Fastify, no koa-style borrowings at runtime. Every abstraction is written from first principles — there's no layer to hide behind when something doesn't work.
- 02
Middleware as a typed pipeline
Each middleware is (ctx, next) => Promise<void>. Composition is a single reducer over an array of handlers. This makes the 'how does Express work?' question visible in about 30 lines of code.
- 03
Trie-based router
Naive string matching breaks down with params, wildcards, and optional segments. A trie pre-compiled at startup gives O(k) lookup by path depth with zero regex at hot path. Same approach used by Fastify and Hono.
- 04
Plugins as adapters, not hooks
Plugins receive the framework instance and extend it explicitly — no implicit beforeEach hooks or global state mutations. Behavior stays traceable from the call site.
Architecture
/ System proof
These are not tool badges. They describe the boundaries, consistency controls, async paths, and failure-mode decisions behind the build.
- 01Typed middleware pipeline (compose reducer pattern)
- 02Trie-based router — O(k) lookup, zero runtime regex
- 03Plugin/adapter system (explicit extension, no implicit hooks)
- 04Custom body parser — zero runtime dependencies
- 05Async error forwarding to error-stage middleware
Challenges & How I Solved Them
Async error propagation
/ Problem
An error thrown inside an async middleware silently hung the request — the response never resolved, and there was no visible indication of failure.
/ Solution
Wrapped the entire middleware pipeline in a try/catch that forwards any thrown error to an error-stage middleware. Same mechanism Express uses internally, but now it's visible in the framework's own core rather than hidden in a dependency.
Router design with params & wildcards
/ Problem
Naive string-split matching breaks immediately when routes include :param segments, optional parts, or wildcard fallbacks. Matching order becomes unpredictable.
/ Solution
Built a trie-based router that pre-compiles all routes once at startup. Each node in the trie is either a static segment, a param node (:id), or a wildcard. Lookup is deterministic and O(k) where k is path depth.
Outcomes
- A complete, working Node.js framework I understand end-to-end
- Reference implementation of the middleware composition pattern — readable in a single file
- Documentation that explains the 'why' behind each design decision, not just the API surface
What I Learned
- 01
Frameworks are a stack of small, individually boring decisions. None of them are clever on their own — the value is in how they compose.
- 02
Writing documentation as you build surfaces design mistakes before they calcify. A concept you can't explain clearly is a concept that isn't designed clearly yet.
- 03
A good core is boring. The interesting parts emerge from composition, not from the primitives themselves.
Tech Stack
Next Steps
- Request validation layer (schema-based, pluggable)
- Streaming response helpers
- Benchmarks vs Express and Fastify to understand the real cost of abstractions