skip to content

fastapi β€” Fast ASGI API Framework

Build high-performance async REST APIs with FastAPI. Covers path params, request bodies, Pydantic models, dependency injection, and auto-generated OpenAPI docs.

3 min read 9 snippets yesterday intermediate

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 uvicorn or hypercorn, or use gunicorn with the uvicorn.workers.UvicornWorker class: 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 def route, it blocks the entire server. Either use async database drivers or declare the route as def (FastAPI runs sync routes in a thread pool automatically).

[!TIP] Declare your route as def (not async 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}