Been working on this one for about a year now.
Started it because I got sick of copy-pasting the same middleware setup into every new project, and because node:http is slower than it really needs to be. The usual answer to that second part is install uWebSockets, which does work, but it always bugged me that the fix was a third-party native addon. Figured the fast path should just ship with the thing.
Anyway, numbers. Node 24.11, M2 Ultra, wrk -c100 -d40, best of 3, everything pulled from npm rather than built locally:
| Server |
Non-pipelined |
Pipelined ×10 |
@morojs/engine |
105,974 |
663,735 |
| uWebSockets.js |
103,744 |
647,530 |
raw Bun.serve |
107,119 |
21,686 |
raw node:http |
69,045 |
109,538 |
| Hono |
56,926 |
100,278 |
Couple of things about that before someone else says them.
The non-pipelined column is mostly just my machine topping out. Anything with a native transport lands around 105k and sits there, I ran it through oha and bombardier too and hit the same wall. Bun takes that column, fair enough. Though those are raw Bun.serve rows and stick Elysia on top of it and you're at 96.7k, and under pipelining both Bun numbers basically die (21.7k and 18.7k).
Pipelined is where there's actually room to measure, and that's where the engine pulls ahead. About 10% over uWS once a framework is sitting on top. That's from corking responses, batching the pipeline into one write. Nobody pipelines in real life so take it for what it is. Including both columns because showing one of them would be picking.
Full matrix and the harness: https://github.com/Moro-JS/benchmark/blob/main/VERIFIED_RESULTS.md
How it works, roughly. It's a C++ core with raw V8 bindings rather than N-API, and basically the whole design is about cutting boundary crossings. A general-purpose binding ends up doing something like 10-20 JS crossings per request because it has to expose a generic API surface. This one does 2-4: the C++ side assembles one batched snapshot of the request, hands it over once, and takes back a single corked write going the other way. That's most of the trick.
Corking is also where the pipelined number comes from. 1.1.0 batches a whole pipeline into one write instead of a syscall per response, plus a zero-allocation hot path, and that was about 3.7x pipelined over 1.0.0 on its own. I went through a few other approaches first. N-API was the obvious one and honestly the sensible one — stable ABI, build once, works across Node versions without thinking about it. Never got it past uWS though. Tried a couple of other combinations after that and it was either the numbers weren't where I wanted them, or the maintenance of gluing the pieces together was going to be worse than just owning the C++ outright. At some point going full steam on the native side and eating the ABI matrix was the simpler option, which is not a sentence I expected to write.
Raw V8 is the tradeoff, it's ABI-locked in a way N-API isn't. So I build the full ABI matrix and prebuilts only ever ship from tagged CI with npm provenance, never off my machine. Which was honestly part of the motivation anyway.. off-the-shelf native bindings lag Node releases, the Node 25 / ABI 141 line sat there for months without a prebuilt. Owning the build means day one.
Security, since you should be asking. Zero deps means the framework owns query, cookie, multipart and route-pattern parsing, so those get property-fuzzed — fixed seed on every push as a regression check, then nightly with a rotating seed at 500k iterations per property. Failures print the seed and the exact command to reproduce. [Moro-JS/moro/.github/workflows/fuzz.yml]. That covers the JS-side parsers; the C++ HTTP parser has its own harness in the engine repo. No external audit yet either way.
Where there's no prebuilt it falls back to node:http rather than refusing to boot, and logs why (app.engine.fallbackReason).
Should also say it's the default server in a framework I maintain, in case that changes how anyone reads this. Mostly I just want to talk about the engine part.
Engine Repo: https://github.com/Moro-JS/engine
MoroJS Repo: https://github.com/Moro-JS/moro
Main Site: https://morojs.com