Benchmarks
Every number here comes from a script in the repo. Run them yourself — numbers nobody can reproduce are marketing, not measurement.
bun benchmarks/http.bench.ts # real sockets, all four frameworksbun benchmarks/dispatch.bench.ts # Request -> Response, no socketbun benchmarks/router.bench.ts # router lookup alonebun benchmarks/image.bench.ts # Bun.Image, and the cost of the upload guardRecorded on Bun 1.4.0 / Node 22.22, Apple Silicon, 2026-08-21.
HTTP throughput
Section titled “HTTP throughput”Real sockets, 50 connections, autocannon, identical routes. Each framework runs on the
runtime it is actually deployed on.
| framework | runtime | static text req/s | param + json req/s | p99 |
|---|---|---|---|---|
| hono | bun | 94,765 | 95,911 | 1 ms |
| oven | bun | 93,805 | 94,087 | 1 ms |
| fastify | node | 80,084 | 77,824 | 1 ms |
| express | node | 18,155 | 18,072 | 5 ms |
Oven is about 5.2× Express and 1.21× Fastify, and level with Hono — 1.9% behind on the parameterised route and 1.0% behind on static text, both inside run-to-run variance.
Dispatch overhead
Section titled “Dispatch overhead”Request in, Response out, with no socket involved. Express and Fastify cannot appear here —
they are built on Node’s req/res and have no Request entry point.
| scenario | oven | hono | on Bun 1.2.23: oven | hono |
|---|---|---|---|---|
| static root | 1051 ns | 406 ns | 1258 ns | 1086 ns |
| param + json | 1255 ns | 916 ns | 1519 ns | 1838 ns |
| 404 miss | 1228 ns | 956 ns | 1530 ns | 1922 ns |
This page used to say Oven dispatched faster than Hono on two of these three rows. On Bun 1.4 that is no longer true. Oven got about 17% faster; Hono got 2.0–2.7× faster and now leads every scenario. The likely cause is in Bun’s own release notes: 1.4 rewrote the RegExp engine, and Hono’s router is regular-expression driven where ours walks a radix tree by hand. A runtime improvement landing squarely on a competitor’s hot path is still a real result.
Read both tables together
Section titled “Read both tables together”This is the point: the two tables do not rank the same way.
Hono dispatches 2.6× faster on a static root and 1.37× faster on a parameterised route. At the socket, those advantages collapse to 1.0% and 1.9%. Once a real connection exists, syscalls and TCP dominate and several hundred nanoseconds of framework overhead vanishes.
The dispatch table is a useful signal about where our own overhead lives, and a bad basis for choosing a framework. We publish both, because publishing only the flattering one would be dishonest — and publishing only the microbenchmark would mislead in the other direction. That was an easy sentence to write when the microbenchmark flattered us. It is the same sentence now that it does not.
Reproducing them
Section titled “Reproducing them”git clone https://github.com/hiteshchoudhary/theoven.gitcd theoven && bun installbun benchmarks/http.bench.tsThe harness starts each server as a separate process, waits for it to answer, runs
autocannon against it, and shuts it down before the next — so no framework is measured while
another holds memory or a JIT-warm CPU. Node contenders run under node, Bun contenders under
bun, because comparing a framework on a runtime nobody deploys it to measures nothing.
Every server implements the same two routes and returns the same bytes:
const app = createApp({ logger: silentLogger })app.get('/', () => 'ok')app.get('/users/:id', (ctx) => ({ id: ctx.params.id }))app.listen(Number(Bun.env.PORT))Numbers will differ on your machine — laptops thermally throttle, and the load generator shares a CPU with the server. What should hold is the ordering and the rough ratios. If it does not, that is worth an issue.
What none of this measures
Section titled “What none of this measures”Every framework here is fast compared to a 2 ms database query. Oven is not trying to win a
router microbenchmark; Bun makes everything fast. What we compete on is the time between
bun create and an app shaped like production — and no benchmark measures that.
Caveats worth knowing: loopback only, empty handlers, and the load generator shares a CPU with the server, which compresses the top of the table. Treat anything above 90k req/s as “at the ceiling”.
Image processing
Section titled “Image processing”Bun.Image, which is what the image brick is built on. The number that
matters is not how fast a resize is — it is how much cheaper reading a header is than decoding
the pixels, because that ratio is the whole basis for guarding an upload.
| source | on the wire | header read | resize + encode |
|---|---|---|---|
| 800×600 (0.5 MP) | 3 KB | 0.8 µs | 3.8 ms |
| 2000×1500 (3.0 MP) | 15 KB | 0.4 µs | 7.7 ms |
| 4000×4000 (16 MP) | 70 KB | 0.6 µs | 44.4 ms |
Reading the header is constant time; decoding is linear in pixels. A 70 KB request body carrying 16 megapixels is refused in well under a microsecond, and the gap only widens as the attack gets bigger — which is why a byte limit alone is not a defence.
Image work also does not block the event loop: a 110 ms resize saw 87 timer ticks. A resize in a handler costs that request its latency, not the whole process’s.