The benchmark scheme is taken from the Fastify GitHub repository except that we intall @fastify/middie, their middleware engine, to be closer to the reality.
- Machine: MacBook Pro (M1 Pro) 32GB RAM
- Method: autocannon -c 100 -d 40 -p 10 localhost:3000 * 2, taking the second average
| Framework | Version | Router? | Requests/sec |
|---|---|---|---|
| Fastify | 4.28.1 | ✓ | 111,985.6 |
| AdonisJS Http Server | 7.2.4 | ✓ | 101,992 |
You can run the same benchmarks by cloning the repo and then running the following command.
npm run benchmarkThis workload measures the complete HTTP request pipeline with one static GET / route. It does not measure how routing performance changes with route count or alternating URLs.
The router benchmark registers 1,000 static routes and 100 dynamic routes in both AdonisJS HTTP Server and Fastify. It measures the first and last static and dynamic routes, alternating static and dynamic URLs, and URLs spread over the whole table.
npm run benchmark:routerEach scenario runs for 10 seconds after a shared warmup. The workload can be adjusted with BENCH_DURATION, BENCH_WARMUP_DURATION, BENCH_CONNECTIONS, BENCH_PIPELINING, BENCH_STATIC_ROUTES, and BENCH_DYNAMIC_ROUTES. Set BENCH_OUTPUT to save the raw results as JSON and BENCH_LABEL to identify the run.
BENCH_SHAPE selects how the route table is built. uniform (the default) declares every static route before any dynamic one and has no catch-all. app mirrors how routes accumulate in a real application: a dynamic route appears early among the static ones, and a top level param route plus a catch-all close the file. Route matching is sensitive to both, so a router change should be measured against each.
BENCH_SHAPE=app BENCH_STATIC_ROUTES=60 BENCH_DYNAMIC_ROUTES=20 npm run benchmark:routerScenarios built from a single path measure a repeated URL, which per-URL memoisation answers without running the matcher. The varied-* scenarios spread requests across the table and are the ones that reflect mixed traffic.
See the recorded before-and-after results for both table shapes.
Since the program correctness and reliability is more important over micro optimizations. We pay penalty on following fronts in comparison to Fastify.
- The AdonisJS query string parser can parse arrays inside the query string
(/api?foo[]=bar&foo[]=fuzz&foo[]=buzz ), wherease fastify doesn't parse it by default for performance reasons. However, you can also define your own query string parser with fastify, but again, you will end up paying the same performance penalty. - Subdomain based routing is another front, where AdonisJS has to perform little bit extra work to find the correct route and it's handler by matching the domains first.