SlowAPI¶
One handler. Two protocols.
A Python web framework that is a WSGI application and an ASGI application at the same time. Express ergonomics, FastAPI typing, NestJS structure — and zero required runtime dependencies.
The whole idea, in one file¶
from slowfw import SlowAPI
app = SlowAPI()
@app.get("/users/{id:int}")
def get_user(id: int) -> dict: # (1)!
return {"id": id}
@app.get("/fanout")
async def fanout() -> dict: # (2)!
return {"results": await gather_everything()}
- A synchronous handler. On WSGI it runs with no event loop involved at all.
- An asynchronous handler, in the same file, served by the same object.
gunicorn app:app --workers 4 # WSGI — and the async handler still works
uvicorn app:app # ASGI — and the sync handler still works
Same file. No wsgi.py shim. No rewrite. No decision made in week one that you
have to live with in year two.
Start here¶
-
:material-download: Installation
pip install slowfw. Nothing else is required — every dependency is an opt-in extra. -
:material-rocket-launch: Quickstart
A working, tested, validated service in about ten minutes.
-
:material-swap-horizontal: Migrating
What maps to what, coming from Flask, FastAPI, Express or NestJS.
-
:material-book-open-variant: The guide
Sixteen chapters, written to be read in order rather than only grepped.
What makes it different¶
It is not a compatibility shim. A fully synchronous request on the WSGI path
never creates an event loop — no task, no scheduler, no thread hop. The dispatch
pipeline is written once as async def so both protocols share one
implementation, then stepped to completion by hand when nothing in the chain can
suspend. How that works.
That is also where the performance difference comes from:
| Route | FastAPI (ASGI) | SlowAPI (ASGI) | SlowAPI (WSGI) |
|---|---|---|---|
| plain text | 154.1µs | 63.3µs | 17.8µs |
| json dict | 155.8µs | 68.7µs | 22.8µs |
| path + query validated | 175.5µs | 78.1µs | 32.8µs |
| async json | 15.6µs | 17.2µs | — |
Median per-request framework overhead, in-process, identical
handlers, byte-identical responses. One laptop, no concurrency. Reproduce it
with python benchmarks/compare.py.
Read that as one result rather than four. The gap is the worker-thread hop,
not the framework. A def handler under ASGI must be offloaded to a thread so
it cannot stall the event loop, and that hop costs more than everything else on
the row combined. On WSGI, SlowAPI never makes it. Where no hop is involved — an
async def handler — the two are level.
So the honest claim is a narrow one: synchronous code is substantially cheaper here, asynchronous code is a wash, and you should be choosing on features and ecosystem, where FastAPI is far ahead.
What is in the box¶
-
:material-check-decagram: Typed and validated
Parameters, bodies and DTOs are coerced and validated from annotations, with or without Pydantic. OpenAPI is generated from the same source.
-
:material-layers-triple: Structure when you need it
Controllers, modules, providers and scoped dependency injection — available when an application grows, absent until then.
-
:material-shield-lock: Secure by default
Signed sessions, path-traversal defences, proxy-header handling and security headers that are on before you configure anything.
-
:material-heart-pulse: Ready for an operator
Liveness and readiness endpoints, background tasks, request deadlines, and startup route validation that fails the build instead of the deploy.
Honest limits¶
This is a young framework, and the useful question is not whether the code is good but whether the project is old enough to trust with someone else's data.
It has no WebSocket support yet, no third-party ecosystem, one maintainer, and no release history. Nobody adversarial has audited it. Those are properties of a project's age, and no amount of engineering shortens them.
Use it for internal tools, side projects and services you own end to end. The FAQ answers this at more length, and why it exists makes the case for the design itself.