FastAPI Reference: Routes, Pydantic, Auth, Async SQLAlchemy, Middleware & Testing
FastAPI is Python’s fastest-growing web framework. It uses Python type hints to auto-generate OpenAPI docs, validate requests, and serialize responses — all with async support out of the box. If you’re writing a Python API in 2026, FastAPI is the default choice unless you have a specific reason to use Flask or Django.
1. Basic App, Routes & Pydantic Models
Create an API, define endpoints, and validate request/response bodies
# pip install fastapi uvicorn[standard]
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, EmailStr, Field
from typing import Optional
app = FastAPI(title="My API", version="1.0.0")
# Pydantic model (request body + response):
class UserCreate(BaseModel):
name: str = Field(min_length=1, max_length=100)
email: EmailStr # validates email format
age: int = Field(gt=0, lt=150) # greater than 0, less than 150
role: str = "user" # default value
class User(BaseModel):
id: int
name: str
email: EmailStr
role: str
model_config = {"from_attributes": True} # allows ORM model → Pydantic (Pydantic v2)
# Path parameters:
@app.get("/users/{user_id}", response_model=User)
async def get_user(user_id: int): # int auto-validated from path
user = db.get_user(user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
# Query parameters + request body:
@app.post("/users", response_model=User, status_code=status.HTTP_201_CREATED)
async def create_user(user: UserCreate, send_welcome: bool = False):
# user = validated UserCreate instance
# send_welcome = ?send_welcome=true/false query param
new_user = db.create_user(user.model_dump())
if send_welcome:
await send_email(new_user.email)
return new_user
# Run locally:
# uvicorn main:app --reload
# API docs: http://localhost:8000/docs (Swagger UI)
# Alt docs: http://localhost:8000/redoc
2. Dependencies & Authentication
Dependency injection, OAuth2 JWT auth, and shared dependencies
from fastapi import Depends, Header
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
import jwt
# Database session dependency (shared across requests):
from sqlalchemy.orm import Session
from database import SessionLocal
def get_db():
db = SessionLocal()
try:
yield db # provide db to endpoint
finally:
db.close() # cleanup after request
# Use in endpoint:
@app.get("/users/{id}")
async def get_user(id: int, db: Session = Depends(get_db)):
return db.query(UserModel).filter(UserModel.id == id).first()
# JWT Bearer authentication:
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def get_current_user(token: str = Depends(oauth2_scheme)) -> User:
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
user_id = payload.get("sub")
if not user_id:
raise HTTPException(status_code=401, detail="Invalid token")
except jwt.PyJWTError:
raise HTTPException(status_code=401, detail="Invalid token")
user = db.get_user(int(user_id))
if not user:
raise HTTPException(status_code=401, detail="User not found")
return user
# Login endpoint (exchange credentials for JWT):
@app.post("/token")
async def login(form: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(form.username, form.password)
if not user:
raise HTTPException(status_code=401, detail="Wrong credentials")
token = jwt.encode({"sub": str(user.id)}, SECRET_KEY, algorithm="HS256")
return {"access_token": token, "token_type": "bearer"}
# Protected endpoint:
@app.get("/me", response_model=User)
async def get_me(current_user: User = Depends(get_current_user)):
return current_user
3. Middleware, CORS & Background Tasks
Request middleware, CORS headers, and fire-and-forget background tasks
from fastapi.middleware.cors import CORSMiddleware
from fastapi import BackgroundTasks
import time, logging
# CORS (required for browser-facing APIs):
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.example.com", "http://localhost:3000"],
allow_credentials=True, # allows cookies/auth headers
allow_methods=["*"],
allow_headers=["*"],
)
# Custom middleware (timing, logging):
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
class TimingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
start = time.time()
response = await call_next(request)
duration = time.time() - start
response.headers["X-Process-Time"] = f"{duration:.3f}s"
logging.info(f"{request.method} {request.url.path} {response.status_code} {duration:.3f}s")
return response
app.add_middleware(TimingMiddleware)
# Background tasks (fire-and-forget after response sent):
async def send_notification(user_id: int, message: str):
await email_service.send(user_id, message) # runs after response
@app.post("/orders")
async def create_order(order: OrderCreate, bg: BackgroundTasks):
new_order = db.create_order(order.model_dump())
bg.add_task(send_notification, new_order.user_id, "Order confirmed!")
return new_order # response sent immediately, notification sent after
4. Database Integration with SQLModel / SQLAlchemy
Async SQLAlchemy 2.0, relationships, and migrations with Alembic
# SQLModel (combines SQLAlchemy + Pydantic — from FastAPI's creator):
# pip install sqlmodel
from sqlmodel import SQLModel, Field, Session, create_engine, select
from typing import Optional
class UserDB(SQLModel, table=True):
__tablename__ = "users"
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(max_length=100)
email: str = Field(unique=True, index=True)
active: bool = True
# Async SQLAlchemy 2.0 (for production async APIs):
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import DeclarativeBase
engine = create_async_engine("postgresql+asyncpg://user:pass@db:5432/mydb")
async def get_db() -> AsyncSession:
async with AsyncSession(engine) as session:
yield session
@app.get("/users/{user_id}")
async def get_user(user_id: int, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(UserDB).where(UserDB.id == user_id))
user = result.scalar_one_or_none()
if not user:
raise HTTPException(404, "Not found")
return user
@app.post("/users", status_code=201)
async def create_user(data: UserCreate, db: AsyncSession = Depends(get_db)):
user = UserDB(**data.model_dump())
db.add(user)
await db.commit()
await db.refresh(user) # reload from DB to get generated fields (id, timestamps)
return user
# Alembic migrations:
# alembic init alembic
# alembic revision --autogenerate -m "add users table"
# alembic upgrade head
5. Testing, Deployment & Performance
Test with httpx, Docker production setup, and async performance tips
# Testing with httpx (async-native test client):
# pip install pytest pytest-asyncio httpx
import pytest
from httpx import AsyncClient
from main import app
@pytest.mark.asyncio
async def test_create_user():
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.post("/users", json={
"name": "Alice", "email": "alice@example.com", "age": 30
})
assert response.status_code == 201
assert response.json()["name"] == "Alice"
@pytest.mark.asyncio
async def test_get_nonexistent_user():
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get("/users/99999")
assert response.status_code == 404
# Production Dockerfile (multi-stage, non-root, small image):
# FROM python:3.12-slim AS base
# RUN pip install uv
# COPY pyproject.toml .
# RUN uv pip install --system .
# COPY src/ /app/src/
# USER nobody
# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
# Performance tips:
# - Use async all the way down (async def endpoints + asyncpg/motor/redis.asyncio)
# - Workers = 2 * CPU_count + 1 (for I/O-bound): --workers $(expr 2 \* $(nproc) + 1)
# - Use response_model_exclude_unset=True to skip serializing None fields
# - Connection pooling: pool_size=10, max_overflow=20 in create_async_engine
# - Add Redis caching with aiocache or fastapi-cache2
# Health check endpoint (for K8s liveness/readiness probes):
@app.get("/health", include_in_schema=False)
async def health():
return {"status": "ok"}
Track FastAPI, Python, and web framework releases.
ReleaseRun monitors Python, Node.js, and 13+ technologies.
Related: Python Reference | Python Packaging Reference | Docker Compose Reference | Python EOL Tracker
🔍 Free tool: PyPI Package Health Checker — check FastAPI, Pydantic, Starlette, and Uvicorn for known CVEs, EOL status, and latest versions.
Founded
2023 in London, UK
Contact
hello@releaserun.com