/Pingstatic 2-byte body - routing + write path
Every server implements the exact same spec - GET / (Ping), GET /id/1?name=bun (Query), POST /json (Body), and GET /video (Video) - and is byte-checked against the expected responses before a single request is fired. Load is generated with bombardier --fasthttp against build artifacts, not dev servers.
Mean score · buntok
82,651req/s
#1 of 5 · +0.8% vs elysia
Correctness
0errors
12,231,113 requests across 5 frameworks × 4 routes
Cold start · buntok
51ms
rank 1/5 · fastest: buntok 51 ms
RSS growth · buntok
+11MB
41.3 MB → 52.2 MB under sustained load
| # | Framework | Runtime | Score (req/s) | P50 ms | P99 ms | Cold start | Bundle | RSS Δ |
|---|---|---|---|---|---|---|---|---|
| 1 | buntok | bun | 82,651 | 5.2 | 30.3 | 51 ms | 828.9 KB | +11 MB |
| 2 | elysia | bun | 82,023 | 5.3 | 31.2 | 51 ms | 375.2 KB | +5 MB |
| 3 | hono | bun | 71,185 | 5.6 | 30.0 | 56 ms | 19.6 KB | +10 MB |
| 4 | fastify | bun | 50,048 | 8.2 | 108.4 | 156 ms | 584.9 KB | +98 MB |
| 5 | express | bun | 20,725 | 19.9 | 109.3 | 106 ms | 805.4 KB | +94 MB |
P50 = median across routes · P99 = worst route · cold start = spawn → first successful GET / · RSS Δ measured after 2 × 10 s of load · bundle = minified Bun.build artifact.
/Pingstatic 2-byte body - routing + write path
/id/1?name=bunQuerydynamic params + query parsing, must echo "1 bun"
POST /jsonBodyJSON parse + mirror of {"hello":"world"}
/videoVideo14.1 MB mp4 stream at 10 connections, no buffering
| Framework | P50 | P90 | P95 | P99 | Max |
|---|---|---|---|---|---|
| / - Ping | |||||
| buntok | 3.5 | 3.6 | 3.8 | 5.0 | 54.0 |
| elysia | 3.5 | 3.7 | 3.8 | 5.3 | 63.5 |
| hono | 4.5 | 4.7 | 4.9 | 6.1 | 73.3 |
| fastify | 6.1 | 6.8 | 7.1 | 11.4 | 116.1 |
| express | 15.0 | 15.6 | 17.3 | 25.6 | 107.2 |
| /id/1?name=bun - Query | |||||
| buntok | 5.0 | 5.2 | 5.3 | 6.2 | 55.2 |
| elysia | 4.9 | 5.1 | 5.2 | 6.1 | 49.9 |
| hono | 5.3 | 5.5 | 5.8 | 7.4 | 48.8 |
| fastify | 6.7 | 7.5 | 7.7 | 10.0 | 45.4 |
| express | 16.3 | 16.9 | 17.3 | 23.6 | 51.5 |
| POST /json - Body | |||||
| buntok | 5.5 | 5.7 | 5.8 | 6.7 | 46.7 |
| elysia | 5.7 | 5.9 | 6.0 | 7.0 | 44.8 |
| hono | 6.0 | 6.3 | 6.4 | 6.9 | 46.0 |
| fastify | 9.7 | 11.1 | 12.5 | 18.3 | 82.1 |
| express | 23.6 | 27.8 | 34.2 | 40.7 | 99.2 |
| /video - Video | |||||
| buntok | 12.2 | 19.0 | 23.5 | 30.3 | 53.4 |
| elysia | 12.2 | 18.8 | 23.3 | 31.2 | 41.1 |
| hono | 12.3 | 19.2 | 23.1 | 30.0 | 63.5 |
| fastify | 94.2 | 99.6 | 101.5 | 108.4 | 121.1 |
| express | 98.0 | 102.3 | 104.4 | 109.3 | 119.8 |
spawn → first successful GET /
minified Bun.build artifact on disk
idle → after 2 × 10 s of sustained load
The four-route spec matches bun-http-framework-benchmark so results are directly comparable there; the alternating rounds + best-of protocol comes from this project's A/B methodology for lower run-to-run noise.
Buntok is a decorator-first, zero-config API framework for Bun. Applications are built as class-based controllers whose routes and middleware are declared with Stage 3 decorators - @Controller, @Get, @Post, @Use - that run without the experimentalDecorators flag. Constructor dependencies are resolved by an IoC container with circular dependency detection, so services are assembled once at boot and injected where they are used.
Performance is a build-time concern rather than a tuning one: the middleware pipeline is compiled once at boot (AOT), static routes resolve in constant time, and dynamic segments match through a trie with an optional native Zig resolver over FFI. TypeScript support runs from route handler to validator - Zod validators via zValidator cover body, query, and params - and middleware such as CORS, compression, rate limiting, and request IDs ships in the core package rather than as a plugin ecosystem.
These benchmarks were run on a AMD EPYC 9V74 80-Core Processor with 16GB RAM - a modest 4-core laptop, not a beefy CI runner, so the numbers are reproducible on real developer hardware. Every framework implements the same four spec routes and is verified against the expected responses before any load is applied. Load is generated with bombardier in fasthttp mode (500 connections, 10 s per route; Video uses 10). Frameworks run in alternating rounds - one warmup pass is discarded and the best of the measured rounds wins per route. Scores shown are exact means of the four route results, and the raw per-round numbers are printed for every route so outliers are visible, not hidden. Buntok is still in active development - the AOT router and middleware pipeline are being optimized. Results may vary across different hardware and configurations.
Source code: benchmarks/runner.ts