Skip to content
Huy Nguyen's Blog
Go back

Python API Framework Benchmark: FastAPI vs Django vs Litestar on Real Database Workloads

Framework benchmarks usually measure one thing: how fast a framework can serialize JSON. That’s a fine number, but it’s not what most of us ship. Most real APIs spend their time talking to a database.

So I benchmarked the major Python frameworks with real PostgreSQL workloads — complex queries, nested relationships, and properly optimized eager loading for each framework (select_related/prefetch_related for Django, selectinload for SQLAlchemy). Every framework was tested with multiple production servers in isolated Docker containers with strict resource limits.

All queries follow each framework’s best practices. This is a comparison of properly written production code, not naive implementations.

TL;DR: Performance differences collapse from 20x (plain JSON) to 1.7x (paginated queries) to 1.3x (complex detail queries). Database I/O is the great equalizer — framework choice barely matters for database-heavy apps.

Benchmark results across all endpoints — the huge JSON gap flattens out once endpoints hit the database

Full results, code, and a reproducible Docker setup: python-api-frameworks-benchmark

Table of contents

Open Table of contents

What was tested

Frameworks and servers:

Setup:

Endpoints:

EndpointDescription
/json-1k~1KB JSON response
/json-10k~10KB JSON response
/db10 simple database reads
/articles?page=1&page_size=20Paginated articles with nested author + tags
/articles/1Single article with nested author + tags + comments

Results

1. Simple JSON (/json-1k)

A 20x difference between fastest and slowest — this is the number most benchmarks stop at.

RPS for a 1KB JSON response, by framework and server

FrameworkRPSLatency (avg)
litestar-uvicorn31,7450.00ms
litestar-granian22,5230.00ms
bolt22,2890.00ms
fastapi-uvicorn12,8380.01ms
fastapi-granian8,6950.01ms
drf-gunicorn4,2710.02ms
drf-granian4,0560.02ms
ninja-granian2,4030.04ms
ninja-uvicorn2,2670.04ms
drf-uvicorn1,5820.06ms

2. Paginated articles (/articles?page=1&page_size=20)

Add a real database query with nested relations and the gap shrinks to 1.7x. Query optimization is now the bottleneck.

RPS for paginated articles with nested author and tags

FrameworkRPSLatency (avg)
litestar-uvicorn2530.39ms
litestar-granian2380.41ms
bolt2370.42ms
fastapi-uvicorn2250.44ms
drf-granian2210.44ms
fastapi-granian2180.45ms
drf-uvicorn1780.54ms
drf-gunicorn1460.66ms
ninja-uvicorn1460.66ms
ninja-granian1420.68ms

3. Article detail (/articles/1)

Single article with all nested data (author + tags + comments): the gap narrows to 1.3x. The frameworks are nearly indistinguishable.

RPS for a single article detail with all nested data

FrameworkRPSLatency (avg)
fastapi-uvicorn5500.18ms
litestar-granian5430.18ms
litestar-uvicorn5190.19ms
bolt4870.21ms
fastapi-granian4800.21ms
drf-granian3670.27ms
ninja-uvicorn3460.28ms
ninja-granian3320.30ms
drf-uvicorn2850.35ms
drf-gunicorn2000.49ms

Complete summary

FrameworkJSON 1kJSON 10kDB (10 reads)PaginatedArticle Detail
litestar-uvicorn31,74524,5031,032253519
litestar-granian22,52317,8271,184238543
bolt22,28918,9232,000237487
fastapi-uvicorn12,8382,3831,105225550
fastapi-granian8,6952,0391,051218480
drf-granian4,0562,817972221367
drf-gunicorn4,2713,423298146200
ninja-uvicorn2,2672,084890146346
ninja-granian2,4032,085831142332
drf-uvicorn1,5821,440642178285

Resource usage

Memory and CPU usage per framework under load

Memory: most frameworks sat at 170–220MB. The outlier was DRF on Granian’s WSGI mode at 640–670MB — a difference in protocol handling (WSGI vs ASGI), not a performance problem.

CPU: most frameworks saturated the 1-core limit under load; the Granian variants consistently maxed out CPU across all frameworks.

Server notes

Takeaways

  1. The performance gap collapses: 20x on JSON serialization → 1.7x on paginated queries → 1.3x on complex queries.
  2. Litestar dominates simple workloads, but FastAPI actually wins the complex detail query. At that point they’re all within noise of each other.
  3. Database I/O is the equalizer. Once you hit the database, framework overhead becomes negligible. Query optimization matters infinitely more than framework choice.

Bottom line: if you’re building a database-heavy API — which most of us are — spend your time optimizing queries, not agonizing over frameworks. Properly optimized, they all perform nearly identically. (And if you want help finding those unoptimized queries in Django, that’s exactly what my nplus1 package is for.)

Everything is reproducible: github.com/huynguyengl99/python-api-frameworks-benchmark. Feedback and PRs welcome.


Share this post:


Previous Post
django-i18n-fields: Multilingual Model Fields for Django
Next Post
DRF Auth Kit: A Complete Authentication Solution for Django REST Framework