Framework
SaltyAom/bun-http-framework-benchmark avatar
SaltyAom/bun-http-framework-benchmark

The Bun HTTP benchmark is mostly about what the load generator does not measure

Compare throughput benchmark from various Bun HTTP framework

575 stars66 forksTypeScriptLicense varies

At a glance

What is it?
This repository compares JavaScript HTTP frameworks across Bun, Node and Deno with a shared four-route contract and bombardier as the load generator, and it writes its own README from a template. Its most interesting design decision is the set of routes the frameworks are not allowed to serve well.
Who is it for?
Adopt this benchmark if you are choosing a JavaScript HTTP framework and want a reproducible harness rather than a blog post's numbers. Skip it if you are deciding between runtimes, since the runtime is a variable in the results and the reported figures come from one specific machine.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 13 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One contract, so the comparison measures the framework

The premise of the benchmark is that a framework comparison is only meaningful if every implementation does the same work, so the repository defines a fixed set of routes that each target must implement. The ping route answers GET on `/` with `Hi` and must set a `Content-Type: text/plain` header, where extra context such as a charset is acceptable. The query route takes a path parameter, a query string and headers, and the request sent is `/id/1?name=bun`, with the expected response being `1 bun`. The body route accepts POST on `/json`, must parse the body to JSON and serialise it back to a string, and receives `{ "hello": "world" }`. The video route streams a file from `public/`. The rule that makes this a real test rather than a hello-world comparison is the ban on shortcuts: the implementation must not use a hardcoded string or index to extract the query string, because the value of `name` may be anything, and the same code has to handle `/id/1?name=bun&id=1` returning `1 bun` rather than `1 bun&id=1`.

The video route is the one that separates the field

The video route is the most discriminating part of the harness. It streams `public/kyuukurarin.mp4` without buffering the whole file in application memory, and the response must carry a `Content-Type: video/mp4` header. It uses 10 concurrent connections instead of the 500 used elsewhere, because the file is 14.1 MB, and the request sends `Cache-Control: no-store` together with a deliberately non-matching `If-None-Match` value and requires a 200 response, so every request transfers the full file even when the server emits an ETag. That design choice is what makes the streaming number meaningful, because a framework that quietly buffers the file will look fine on the small routes and fall apart here. The result table shows the split clearly, with the video column ranging from the hundreds of requests per second for the Node-based targets to over 1,600 for the Bun-based ones, while the average column sits in the six-figure range for most rows. The streaming column, in other words, tells you which frameworks are doing real work.

Average throughput is the stated method

The README names the test method plainly: average throughput. Every implementation registers the same deterministic background routes before the measured routes, and those routes are not requested during the load test. The stated reason is that they make route lookup and startup closer to a small real-world service while keeping every run reproducible. The shared path list lives in `src/extra-routes.mjs` and every handler returns `ok`. The load generator is bombardier, and the prerequisites are Node, Deno and Bun, since the point is to compare frameworks across runtimes rather than within one. The comparison also records things other than throughput, since the results table carries columns for the emitted minified bundle size, startup time and server RSS memory as a before and after pair. Deno reports `n/a` for bundle size because it runs directly, and the Bun and Node targets are minified with `Bun.build` before the server startup timer begins, which is the detail that makes the startup column comparable across the two.

The README is generated, so the template is the source

The publishing side of the harness is unusual and worth knowing about if you fork it. Dump results are written to `results/[benchmark-name].txt`, and each run updates `results/results.md` and then generates `README.md` from `README.template.md` with the results and the output of `uname -a`. The instructions say to edit the template to change the README, because the README itself is an artefact. A selected-target run replaces the table with just those targets, and if no target produces results the run preserves the existing `README.md` rather than emptying it. That last behaviour is a small correctness detail that saves you from a run that wipes your documentation. The repository also carries a `bench.sh` alongside `bench.ts`, a `dev/` directory, a `scripts/` directory, a `visual/` directory, and a `__tests__/` directory, so the harness itself is treated as a tested project rather than a loose set of scripts.

Running the benchmark and verifying without loading

There is a single command to run the whole thing.

bash
bun benchmark

You can narrow it to specific targets by passing their runtime-qualified names.

bash
bun benchmark bun/elysia node/effect

And there is an interactive mode that lets you pick frameworks with the arrow keys and Space, then press Enter and enter an RPS limit, where `0` or a blank means unlimited throughput. The results in that mode include bundle size, startup time and the memory column. There is also a verification path that builds and checks every supported framework without running a load test, which is the one to use first.

bash
bun run verify

The verifier starts frameworks one at a time and checks the three core routes, the streamed video, and all the background routes, skipping any target in the benchmark blacklist. The package manifest wires this to `bun scripts/verify-frameworks.ts`, with the benchmark itself at `bun bench.ts` and a `kill-port` script for port 3000, which matters when a previous run left a server holding the port.

The maintenance rule quietly removes frameworks from the table

There is a requirement section that reads like a policy rather than a feature, and it shapes what the table contains over time. A framework must at least have a latest published release in less than 9 months, otherwise it is classified as unmaintained and removed, unless it is an industry standard, with Express named as the exception. That is a hard filter on the comparison set, and it means the table is not a static catalogue but something that changes as projects go quiet. The dependency list in the manifest shows the breadth of the field being measured, with Express 5, Fastify, Koa with koa-router and koa-bodyparser, Hono, h3, Elysia at a 2.0.0 beta, the Nest packages, Hapi, Oak for Deno, hyper-express, ultimate-express, uWebSockets.js pinned to a specific tag, and the Effect platform packages at 4.0.2. AOT variants of Elysia are included for both Bun and Node, Effect HTTP v4 beta uses one shared router with the official adapter for each runtime, and Deno targets run directly because `Bun.build` has no Deno target. The last push to the repository was on 2026-10-10.

Editorial conclusion

Adopt this benchmark if you are choosing a JavaScript HTTP framework and want a reproducible harness rather than a blog post's numbers. Skip it if you are deciding between runtimes, since the runtime is a variable in the results and the reported figures come from one specific machine. Before you run it, read the route contract, because the requirements for parameter and query extraction are strict, and check the maintenance rule, since a framework without a release in nine months is dropped from the comparison.

Frequently asked questions

What does the Bun HTTP framework benchmark measure?

Average throughput in requests per second across a shared four-route contract, plus the emitted minified bundle size, startup time and server RSS memory as a before and after pair. The load generator is bombardier and the runtimes compared are Bun, Node and Deno.

How does the benchmark prevent frameworks from cheating on the query route?

The implementation must not use a hardcoded string or index to extract the query string, because the value of `name` may be anything. The same code has to return `1 bun` for `/id/1?name=bun&id=1` and `1 ` for `/id/1?id=1`.

Why does the video route use only 10 concurrent connections?

Because the file it streams is 14.1 MB, against 500 connections for the other routes. The request also sends `Cache-Control: no-store` and a deliberately non-matching `If-None-Match` value and requires a 200, so every request transfers the full file even when the server emits an ETag.

What happens to frameworks that stop being maintained?

A framework must have had a latest published release in less than 9 months, otherwise it is classified as unmaintained and removed from the comparison, unless it is an industry standard, with Express named as the exception.

Official sources

  1. Issues
  2. README
  3. SaltyAom/bun-http-framework-benchmark on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/saltyaom-bun-http-framework-benchmark.svg)](https://hysenlabs.com/projects/saltyaom-bun-http-framework-benchmark)

Was this useful?