fastapi β Fast ASGI API Framework#
What it is#
FastAPI is a modern ASGI web framework that uses Python type hints to:
- Validate and parse request data (via Pydantic).
- Auto-generate OpenAPI (Swagger UI) and JSON Schema docs.
- Handle async/await natively.
Itβs consistently the fastest Python web framework in benchmarks and has become the default choice for new Python APIs.
Install#
pip install fastapi
pip install "uvicorn[standard]" # ASGI server to run it
Quick example#
# main.py
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}
uvicorn main:app --reload
curl -s "http://127.0.0.1:8000/items/42?q=search"
Output:
{"item_id":42,"q":"search"}
Browse to http://127.0.0.1:8000/docs for the interactive Swagger UI β automatically generated from your type hints.
When / why to use it#
- New async APIs that benefit from non-blocking I/O (database calls, external HTTP).
- When you want OpenAPI docs with zero extra effort.
- Projects using Pydantic for validation β FastAPI integrates it natively.
Prefer Flask for simple sync-only apps with an existing WSGI ecosystem. Prefer Django when you need admin, auth, and ORM batteries.
Common pitfalls#
[!WARNING] FastAPI is ASGI β it needs an ASGI server β do not run it with gunicorn alone. Use
uvicornorhypercorn, or usegunicornwith theuvicorn.workers.UvicornWorkerclass:gunicorn main:app -k uvicorn.workers.UvicornWorker.
[!WARNING] Sync functions in async routes block the event loop β if you call a blocking I/O function (e.g. a synchronous SQLAlchemy query) inside an
async defroute, it blocks the entire server. Either useasyncdatabase drivers or declare the route asdef(FastAPI runs sync routes in a thread pool automatically).
[!TIP] Declare your route as
def(notasync def) when the body is synchronous β FastAPI will run it in a thread pool, keeping the event loop free.
Richer example β request body, dependency injection, and error handling#
# main.py
from fastapi import FastAPI, Depends, HTTPException, status
from pydantic import BaseModel
app = FastAPI(title="Item Store", version="1.0")
ITEMS: dict[int, str] = {1: "Widget", 2: "Gadget"}
class NewItem(BaseModel):
name: str
def get_item_or_404(item_id: int) -> str:
"""Dependency: resolves an item by ID or raises 404."""
item = ITEMS.get(item_id)
if item is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail="Item not found")
return item
@app.get("/items")
def list_items() -> list[dict]:
return [{"id": k, "name": v} for k, v in ITEMS.items()]
@app.get("/items/{item_id}")
def read_item(name: str = Depends(get_item_or_404)) -> dict:
return {"name": name}
@app.post("/items", status_code=status.HTTP_201_CREATED)
def create_item(item: NewItem) -> dict:
new_id = max(ITEMS) + 1
ITEMS[new_id] = item.name
return {"id": new_id, "name": item.name}
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_item(item_id: int):
if item_id not in ITEMS:
raise HTTPException(status_code=404, detail="Item not found")
del ITEMS[item_id]
curl -s http://127.0.0.1:8000/items
curl -s http://127.0.0.1:8000/items/1
curl -s http://127.0.0.1:8000/items/99
curl -s -X POST http://127.0.0.1:8000/items \
-H "Content-Type: application/json" \
-d '{"name":"Thingamajig"}'
Output:
[{"id":1,"name":"Widget"},{"id":2,"name":"Gadget"}]
{"name":"Widget"}
{"detail":"Item not found"}
{"id":3,"name":"Thingamajig"}
Async example β non-blocking route#
import asyncio
import httpx
from fastapi import FastAPI
app = FastAPI()
@app.get("/proxy")
async def proxy():
async with httpx.AsyncClient() as client:
r = await client.get("https://httpbin.org/get")
return r.json()["headers"]
Path, query, and body parameters#
from fastapi import FastAPI, Query, Path
from pydantic import BaseModel
app = FastAPI()
@app.get("/users/{user_id}/posts/{post_id}")
def get_post(
user_id: int = Path(ge=1), # path param, must be >= 1
post_id: int = Path(ge=1),
include_draft: bool = Query(False), # query param with default
):
return {"user_id": user_id, "post_id": post_id, "draft": include_draft}