I've been building zen.js, an open-source, compiler-first web framework for Node.js, and I want to share the idea behind it.
The question that started this project was simple:
What if a web framework compiled your application before the first request instead of repeatedly interpreting its structure at runtime?
Not a new wrapper around an existing framework. Not another routing library with a different API. A different way to think about how a Node.js framework should work.
GitHub: https://github.com/erenthedeveloper0/zen
The idea behind zen.js
Most web frameworks let you define routes, middleware, validation, and response handling through familiar abstractions. Those abstractions make applications easier to build, but they also give the framework information about your application that can potentially be resolved before requests arrive.
zen.js explores what happens when we do more of that work at startup.
The central idea is:
A web framework is a compiler with an HTTP server attached.
In zen.js, application registration acts as the input. At startup, the framework builds a frozen application graph and compiles route-specific request pipelines from it.
Conceptually, a request follows this path:
Route → Parameters → Middleware → Validation → Handler → Serialization → Response
The important distinction is that the framework doesn't need to repeatedly interpret the same higher-level application structure to execute that path.
Stages a route doesn't use can be absent from the generated code rather than sitting in a pipeline waiting to be skipped.
This isn't a native-code compiler. It's about compiling framework metadata into the JavaScript that executes your application's request path.
What does it look like to use?
I wanted the basic API to feel familiar rather than forcing developers to learn an entirely new way of building servers.
Here's a minimal example:
import { zen } from "@erenthedeveloper0/zen";
const app = zen();
app.get("/", () => "Hello world");
await app.listen(3000);
Install the current alpha:
npm install @erenthedeveloper0/zen@alpha
Two basic concepts get you started: register a handler with app.METHOD(path, handler), and return the response from that handler.
The interesting part isn't the number of lines in this example. It's what happens to the application definition after registration.
1. Compile the request path, not just the route table
Routing is only one part of a request.
Middleware ordering, validation, hooks, response serialization, and the request context all affect execution. zen.js attempts to resolve as much of that structure as possible during startup.
That also opens the door to better diagnostics.
For example, conflicting or ambiguous routes can be rejected at boot instead of letting registration order silently determine which route wins. The framework can explain a resolved route's execution chain, and its request-inspection tools can show which steps ran and how they behaved.
The generated code is inspectable, too. I want the architecture to be something developers can examine and challenge, not a black box hidden behind a performance claim.
2. Let schemas define the contract
The second major idea is that a schema should be more than a validation step.
In zen.js, the same contract can drive:
- Input validation and handler types.
- Response serialization and filtering.
- OpenAPI document generation.
For example, when a route declares a response schema, the generated serializer can exclude fields that aren't part of that schema.
That matters for more than performance. An application might return a database record containing internal fields that should never reach a client. The response contract should define what's allowed on the wire.
The goal is to keep the implementation, type system, serialized response, and API documentation aligned instead of maintaining several separate versions of the same contract.
zen.js supports the Standard Schema ecosystem, so libraries such as Zod, Valibot, and ArkType can be used without framework-specific adapters.
3. Make correctness visible
Performance is one motivation, but I don't want the project to be only about throughput.
I'm also interested in making framework behavior easier to understand and verify.
Some examples of what's implemented today include:
- Boot-time route conflict analysis with detailed diagnostics.
- Compiled middleware and lifecycle hooks.
- Schema-driven response serialization.
- OpenAPI 3.1 generation from the application graph.
- Request deadlines, health checks, and readiness handling.
- Request-inspection tools and application graph fingerprints.
These features are part of a broader attempt to make framework behavior explicit, inspectable, and testable.
The architecture is ambitious, and having a detailed architecture document doesn't mean every planned subsystem is already implemented. I'm trying to keep that distinction clear as the project evolves.
What about performance?
I don't want to claim that zen.js is the fastest Node.js framework.
The repository includes benchmarks comparing workloads against other frameworks and Node's HTTP server, along with the measurement methodology and limitations. The results vary by workload, and the project still has room to improve.
The point of the compiler-first approach is to make runtime work more deliberate. Benchmarks are one way to evaluate whether that design delivers practical benefits; they aren't a substitute for understanding the trade-offs.
I'd rather publish measurements that can be reproduced and criticized than lead with a headline number that hides the details.
Where the project stands
zen.js is currently at 0.2.0-alpha.
The current implementation is a working vertical slice, but the project is not production-ready. The API can change before 1.0, and some parts of the broader architecture are still unimplemented.
It currently targets Node.js 22.6 or newer, with TypeScript 5.0 or newer when using TypeScript. The core package has zero runtime dependencies.
I'm sharing it at this stage because architecture is easier to challenge while a project is still evolving than after its decisions have become difficult to change.
I'd genuinely appreciate your feedback
I'm particularly interested in hearing from developers who have worked on Node.js frameworks, routers, compilers, TypeScript-heavy backends, or API tooling.
A few things I'd like to understand:
- Is compiling the application graph at startup a useful architectural direction, or am I moving complexity from runtime into boot?
- Which framework abstractions do you think can be eliminated or simplified through compilation?
- Where would you expect this design to become difficult to maintain?
- What benchmarks, edge cases, or real-world workloads should I investigate next?
You don't need to adopt zen.js or agree with the approach. A thoughtful architectural criticism is just as useful as a feature request.
Explore zen.js
GitHub: https://github.com/erenthedeveloper0/zen
Architecture: https://github.com/erenthedeveloper0/zen/blob/main/ARCHITECTURE.md
Benchmarks: https://github.com/erenthedeveloper0/zen/tree/main/benchmarks
Changelog: https://github.com/erenthedeveloper0/zen/blob/main/CHANGELOG.md
If the idea interests you, I'd appreciate a look at the code, an issue describing a potential weakness, or a discussion about the architecture.
I'm building zen.js in the open, and feedback from people who understand these systems can help me make better decisions before the project reaches 1.0.
Top comments (0)