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.

  1. install uv add notslowapi[granian]
  2. pip pip install "notslowapi[granian]"
  3. import from notslowapi import FastAPI
  4. run granian --interface asgi --workers 1 --loop uvloop myapp:app

Same API, same test suite, drop-in.

Version 0.1.0Python 3.10 or newerMIT

Read the docs

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.

  • item_id: int item_id: int item_id: int item_id: int

    Annotations read once

    Parameter extraction stopped introspecting type annotations on every request.

  • Response path body cookies

    Dependencies only when asked

    The dependency solver skips work no endpoint asked for: the throwaway Response, unused query and cookie parsing.

  • middleware exit stack handler

    Layers that exist on demand

    Per-request exit stacks and middleware layers exist only when a route needs them.

  • /api/items/{id} static paths, indexed

    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.

  1. Install

    Adds notslowapi together with the granian extra. With pip: pip install "notslowapi[granian]".

    uv add notslowapi[granian]
  2. Swap the import

    One line changes. Nothing else in your app does.

    from notslowapi import FastAPI
  3. 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]