FastAPI, minus the slow.
notslowapi is a fork of FastAPI made faster, measured layer by layer. Same API, same test suite, drop-in. On one core, on granian, it serves 3.4× the requests per second of FastAPI 0.141 on uvicorn for a plain JSON route.
-
install
uv add notslowapi[granian] -
pip
pip install "notslowapi[granian]" -
import
from notslowapi import FastAPI -
run
granian --interface asgi --workers 1 --loop uvloop myapp:app
Same API, same test suite, drop-in.
Works alongside the stack you already run
- Pydantic
- Starlette
- Granian
- uvicorn
- SQLAlchemy
- httpx
- pytest
Same code. Fewer microseconds.
One core of an Apple M3 Pro, Python 3.13, 64 keep-alive connections, median of three 5-second runs. Microseconds per request; lower is better.
| Route | FastAPI 0.141 on uvicorn | notslowapi on uvicorn | notslowapi on granian |
|---|---|---|---|
| Plain JSON route | 31.2 µs32,000 req/s | 18.3 µs54,800 req/s | 9.1 µs110,200 req/s |
| int path + str query param | 57.0 µs | 24.2 µs | 14.3 µs |
| pydantic body + response_model | 52.1 µs | 26.1 µs | 20.1 µs |
| 50 routes via include_router | not measured | 26.3 µs | 17.3 µs |
Single-core numbers; multi-core scaling needs a separate client machine. The 50-route case was not part of the day-one run. Numbers and profiles are committed under bench/baseline; the benchmarks page has every rung and every change measured before and after.
How it got faster
Less work per request. Dashed is what a request no longer pays for; the solid line is the path that remains.
-
Annotations read once
Parameter extraction stopped introspecting type annotations on every request.
-
Dependencies only when asked
The dependency solver skips work no endpoint asked for: the throwaway Response, unused query and cookie parsing.
-
Layers that exist on demand
Per-request exit stacks and middleware layers exist only when a route needs them.
-
Routers matched once
Routes reached via include_router are matched once, not twice, and static paths are indexed.
-
One exception layer
One exception-handling layer instead of three, and one frame from router to handler.
-
Direct encoding
JSON encoding and query-string parsing run directly, without stdlib wrapper layers.
Switch in a minute
Three steps. Your routes, dependencies and tests stay as they are.
-
Install
Adds notslowapi together with the granian extra. With pip:
pip install "notslowapi[granian]".uv add notslowapi[granian] -
Swap the import
One line changes. Nothing else in your app does.
from notslowapi import FastAPI -
Run
Start the same app on granian with uvloop.
granian --interface asgi --workers 1 --loop uvloop myapp:app
Deploy on granian
Use granian. Its Rust I/O threads run alongside the Python thread instead of sharing it. If you stay on uvicorn, the plain route still runs 1.7× faster than FastAPI 0.141; pass the three flags below.
granian
The measured setup: ASGI interface, one worker, uvloop.
granian --interface asgi --workers 1 --loop uvloop myapp:app
uvicorn
Pass these three flags unless a proxy in front sets X-Forwarded-* headers. Together they are worth about 5 percent.
uvicorn myapp:app --no-proxy-headers --no-server-header --no-date-header
Everything else stays identical
Same API, same test suite, drop-in. The parts of FastAPI you build on are the parts that did not move.
- Routing
- Dependencies
- OpenAPI
- Validation
Starlette is vendored and tuned inside notslowapi as notslowapi.starlette; nothing is installed under the starlette name.
Measured, not estimated.
Every change was measured before and after on a ladder of ASGI rungs, with native sampling from sample(1) and pyinstrument, alternating after/before/after runs to reject noise. The numbers and profiles are committed under bench/baseline.
-
3.4×
throughput per core
Plain JSON route, notslowapi on granian against FastAPI 0.141 on uvicorn: 110,200 req/s against 32,000.
-
2.4×
on typed params
int path + str query param, both on uvicorn: 57.0 µs per request down to 24.2.
-
2.0×
on a pydantic body
pydantic body + response_model, both on uvicorn: 52.1 µs per request down to 26.1.
Questions
Is it really drop-in?
Yes. notslowapi is a fork of FastAPI with the same API and the same test suite. Swap the import and run. The one edge: Starlette is vendored under notslowapi, so code that imports starlette.* directly needs upstream Starlette installed and gets upstream behavior for those objects.
Which Python versions?
Python 3.10 or newer. The current release is 0.1.0 on PyPI.
Do the numbers hold across cores?
The published numbers are single-core: one core of an Apple M3 Pro, Python 3.13, 64 keep-alive connections. Multi-core scaling needs a separate client machine, so it is not measured here.
What is the license?
MIT, inherited from FastAPI, plus BSD for the vendored Starlette.
Drop it into your app today
One dependency, one import. The same tests, fewer microseconds.
uv add notslowapi[granian]