fastapi
The dominant modern Python web framework for building APIs. You declare endpoint parameters and request bodies with standard Python type hints, and FastAPI handles validation, serialization, editor autocomplete, and an auto-generated OpenAPI spec with interactive Swagger and ReDoc pages for free. Under the hood it is a thin layer over Starlette (ASGI web parts) and Pydantic (data validation), which is where its speed comes from. It runs sync or async endpoints side by side and has become the default choice for Python API services, especially around ML model serving.
The right default for Python JSON APIs in 2026: the type-hint-driven developer experience is real and the docs walk you through everything. Just respect the async footguns, expect to assemble your own stack around it, and do not pick it for content-heavy sites that Django solves in an afternoon.
Use it if
- You are building a JSON API in Python and want request validation and OpenAPI docs generated from type hints instead of maintained by hand
- You need async I/O: it is ASGI-native, so websockets, streaming, and high-concurrency I/O-bound endpoints work without bolt-ons
- You serve ML models: type-hinted request schemas plus async workers is why teams at Uber and Netflix cited it, per the README
- You already use Pydantic models elsewhere and want the same models to define your API contract
- You are building a server-rendered site with forms, sessions, an admin, and an ORM: Django gives you all of that in the box, FastAPI gives you none of it
- Your team does not understand async: writing blocking calls (requests, classic ORMs) inside async def endpoints silently serializes your whole event loop, and this is the single most common FastAPI production bug
- You want a stable 1.0 contract: after seven-plus years it is still 0.x (0.141.1), and minor releases have moved behavior (Pydantic v1 to v2 was a painful migration wave)
- You dislike depending on one person's project: despite a grown team and now a company (FastAPI Cloud) around it, direction still runs substantially through its creator
- You need heavy CPU-bound request handling: async Python buys you nothing there and the framework cannot fix the GIL
Setup reality
pip install "fastapi[standard]" gets you the framework plus uvicorn and the fastapi dev CLI; plain pip install fastapi leaves you to install a server yourself, which trips up beginners. Development is genuinely pleasant (fastapi dev auto-reloads, /docs is instant gratification). Production is more assembly: uvicorn workers behind a process manager or gunicorn, plus your own choices for ORM, migrations, auth, and background jobs, none of which are included. The big conceptual tax is def vs async def: sync endpoints run in a threadpool, async ones on the event loop, and mixing blocking libraries into async def is an easy, quiet way to wreck throughput.
Patterns
Minimal app with typed paramshello-api
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}Run with: fastapi dev main.py (from fastapi[standard]); path params come from the URL, other args become query params.
Validate a JSON body with Pydanticrequest-body
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
is_offer: bool | None = None
@app.post("/items/")
def create_item(item: Item):
return {"name": item.name, "price": item.price}Invalid payloads get an automatic 422 with field-level errors; you write no validation code.
Filter output with a response modelresponse-model
class UserOut(BaseModel):
id: int
email: str
@app.get("/users/{user_id}", response_model=UserOut)
def get_user(user_id: int):
user = fetch_user(user_id) # may contain hashed_password etc.
return userresponse_model strips fields not in the schema, which is the supported way to keep secrets out of responses.
Choose def vs async def correctlysync-vs-async
import httpx
@app.get("/sync")
def sync_endpoint():
return requests.get(url).json() # ok: runs in threadpool
@app.get("/async")
async def async_endpoint():
async with httpx.AsyncClient() as client:
r = await client.get(url) # ok: truly async
return r.json()Never call blocking libraries inside async def; it blocks the event loop for every request. Plain def is the safe default.
Share logic with Dependsdependency-injection
from fastapi import Depends
from typing import Annotated
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/orders")
def list_orders(db: Annotated[Session, Depends(get_db)]):
return db.query(Order).all()yield dependencies run cleanup after the response; the Annotated form is the current documented style.
Protect endpoints with an OAuth2 bearer tokenauth-bearer
from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def current_user(token: Annotated[str, Depends(oauth2_scheme)]):
user = decode_token(token)
if not user:
raise HTTPException(status_code=401)
return user
@app.get("/me")
async def me(user: Annotated[User, Depends(current_user)]):
return userThis only extracts and gates on the token; issuing JWTs, hashing passwords, and refresh flows are your code (the docs have a full recipe).
Return proper HTTP errorserror-handling
from fastapi import HTTPException
@app.get("/items/{item_id}")
def read_item(item_id: int):
item = store.get(item_id)
if item is None:
raise HTTPException(status_code=404, detail="Item not found")
return itemRaise HTTPException, do not return it; returning one serializes the exception object as a 200 body.
Accept a file uploadfile-upload
from fastapi import UploadFile
@app.post("/upload")
async def upload(file: UploadFile):
contents = await file.read()
return {"filename": file.filename, "size": len(contents)}UploadFile requires python-multipart installed (included in fastapi[standard]); file.read() loads it all into memory, stream large files instead.
Run work after the responsebackground-tasks
from fastapi import BackgroundTasks
@app.post("/signup")
def signup(email: str, background_tasks: BackgroundTasks):
background_tasks.add_task(send_welcome_email, email)
return {"status": "ok"}Tasks run in-process after the response and die with the worker; for retries or heavy jobs use Celery or another real queue.
Enable CORS for a frontendcors
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.example.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)allow_origins=["*"] cannot be combined with allow_credentials=True; browsers will reject the response.
Test with TestClienttest-endpoints
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_read_item():
r = client.get("/items/5?q=x")
assert r.status_code == 200
assert r.json() == {"item_id": 5, "q": "x"}TestClient needs httpx installed; it calls the app in-process, no server required, and works for sync tests of async endpoints.
Run in productionrun-production
fastapi run main.py --workers 4 --port 8000
# or explicitly:
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4fastapi dev is development-only (auto-reload, localhost); put a reverse proxy in front and size workers to CPU cores.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| django | PyPI | Full-stack apps that want ORM, admin, auth, and migrations included, with DRF or Django Ninja for the API layer |
| flask | PyPI | Small sync services or teams that want a minimal, decades-stable WSGI framework and will add validation themselves |
| litestar | PyPI | A similar typed ASGI framework governed by a maintainer team instead of one lead, with built-in DTOs |
| starlette | PyPI | You want the bare ASGI toolkit FastAPI is built on, without validation or OpenAPI machinery |