Benchmarks¶
Django-Bolt is the fastest Python web framework we have measured: ~311,000 requests/second on a JSON hello-world from a single 12-core desktop, ~187,000 req/s for a 10 KB JSON body, and 21,000–27,000 req/s for a 10-row Django ORM query. Every number below states its conditions and can be reproduced with one command.
Read this first
Absolute numbers are hardware-specific. Publish the conditions with the number — the project holds itself to that rule, and you should too when you quote these figures. Run-to-run spread on the reference machine is about ±5%; treat gaps under ~10% as a tie.
Reference setup¶
| Machine | AMD Ryzen 5 5600G (6 cores / 12 threads), 16 GB RAM, Linux |
| Load generator | bombardier, loopback, on the same machine |
| Concurrency | C=100 connections, N=100000 requests per endpoint |
| Server | python manage.py runbolt --processes 8 (8 processes × 1 Actix worker, SO_REUSEPORT) |
| App | python/example — full Django project with admin, sessions, CSRF, messages, CSP middleware installed |
| Django-Bolt | 0.10.0 · results file: python/benchmark/BENCHMARK.md (2026-07-28) |
Because bombardier shares the 12 cores with the 8 server processes, the load generator is part of the system under test. Read the numbers as a floor, not a ceiling.
Results by endpoint¶
| Endpoint | What it exercises | Req/s | p50 | p99 |
|---|---|---|---|---|
GET / |
JSON hello-world, routing overhead | 311,270 | 234 µs | 2.20 ms |
GET /html |
HTML response | 285,927 | — | — |
GET /items/1?q=hello |
Path + query params, typed | 264,152 | — | — |
PUT /items/1 |
JSON body → msgspec.Struct |
256,945 | — | — |
GET /header, /cookie |
Header / cookie extraction | ~255,000 | — | — |
POST /bench/parse |
JSON parse + validate (Django Ninja-style) | 251,426 | — | — |
GET /exc |
Raised HTTPException → error response |
247,087 | — | — |
POST /form |
URL-encoded form | 217,731 | — | — |
GET /cbv-simple |
Class-based APIView |
219,367 | — | — |
GET /sync-10k-json |
10 KB JSON response (sync handler) | 187,186 | 473 µs | 2.17 ms |
GET /10k-json |
10 KB JSON response (async handler) | 184,059 | 475 µs | 1.78 ms |
POST /upload |
Multipart file upload | 178,364 | — | — |
GET /auth/context |
JWT validated in Rust, no DB | 158,060 | — | — |
GET /static/…/asset_1k.css |
Static file, 1 KB | 159,114 | — | — |
GET /static/…/asset_100k.js |
Static file, 100 KB | 94,226 | — | — |
GET /bench/list |
List of 100 structs | 89,229 | — | — |
GET /file-static |
FileResponse |
47,177 | — | — |
GET /auth/me |
JWT + load request.user from DB |
40,312 | — | — |
GET /users/mini10 |
ORM: 10 rows, 2 fields, SQLite, async | 26,694 | — | — |
GET /users/full10 |
ORM: 10 full rows, SQLite, async | 20,963 | — | — |
GET /users/sync-full10 |
ORM: 10 full rows, SQLite, sync handler | 15,767 | — | — |
GET /middleware/demo |
Full Django middleware stack + messages framework | 8,744 | — | — |
The ORM rows are bounded by SQLite's single-writer file lock and by the ORM's own Python cost, not by Bolt; on PostgreSQL the ORM executor uses more threads. The middleware row runs the entire settings.MIDDLEWARE chain (sessions, CSRF, auth, messages, CSP) in Python for every request — it is there to show that nothing is stripped, and what the full stack costs.
Streaming: Server-Sent Events¶
10,000 concurrent SSE clients held open for 60 seconds:
| Metric | Value |
|---|---|
| Successful connections | 10,000 / 10,000 (100%) |
| Aggregate throughput | 9,489 messages/s |
| Messages per client | 57.3 average |
| CPU | 11.9% average (peak 101.9% of one core) |
| Memory | 236 MB RSS |
Against JavaScript runtimes¶
Same JSON payloads, same machine, same load generator, byte-identical response bodies checked on every run. C=100, N=100000.
8 processes each (Bolt: --processes 8; Hono/Node: node:cluster; Bun: 8 processes with reusePort):
| Payload | Django-Bolt | Elysia / Bun | Hono / Bun | Hono / Node |
|---|---|---|---|---|
| 1 KB JSON | 251,203 | 264,037 | 209,760 | 96,639 |
| 10 KB JSON | 157,462 | 123,535 | 110,633 | 79,110 |
Single process:
| Payload | Django-Bolt | Elysia / Bun | Hono / Bun | Hono / Node |
|---|---|---|---|---|
26 B JSON (/) |
72,766 | 88,379 | 67,714 | 35,263 |
| 1 KB JSON | 61,396 | 75,625 | 58,137 | 29,729 |
| 10 KB JSON | 35,987 | 30,673 | 24,981 | 17,977 |
Reading: Elysia leads on tiny payloads at one process (1.2×) and ties Bolt at eight (1.05×, inside the noise). Bolt leads on 10 KB at both scales (1.17× / 1.27×) — once serialization and body handling dominate, msgspec plus the zero-copy response path win. A Python framework beating Bun-native servers on realistic payload sizes is the headline here.
Against Python frameworks¶
The first benchmark committed to the repository (2025-09-20, same machine, C=50, N=10000, JSON hello-world, one process each, default settings) measured:
| Framework | Req/s | Mean latency |
|---|---|---|
| Django-Bolt (day-1 build) | 43,240 | 1.2 ms |
| Robyn | 11,246 | 4.4 ms |
| FastAPI + uvicorn | 3,813 | 13.1 ms |
Django-Bolt's own number has since grown ~7× on the same machine (43k → 311k at 8 processes; 73k at one process), so treat this table as a lower bound on the gap. A fresh, multi-framework harness (FastAPI, Litestar, Django Ninja, Robyn, DRF — same payloads as bench/js) is planned; until it lands, the JavaScript comparison above is the most rigorous cross-runtime data the project publishes.
Reproduce¶
git clone https://github.com/dj-bolt/django-bolt.git && cd django-bolt
uv sync && just build
go install github.com/codesenberg/bombardier@latest
just save-bench # full suite → python/benchmark/BENCHMARK.md
just save-bench 127.0.0.1 8001 100 100000 8 # host port C N processes
How to quote these numbers¶
- ✅ "Django-Bolt: ~311k req/s JSON hello-world, 8 processes, C=100, Ryzen 5 5600G, loopback bombardier."
- ✅ "Django-Bolt beats Bun/Elysia by 1.27× on 10 KB JSON at 8 processes (same machine)."
- ❌ "Django-Bolt does 300k RPS" with no conditions.
- ❌ Comparing to numbers measured on a different machine or day.