FastAPI
FastAPI routes, request models, dependency injection, authentication, and async patterns.
Routes & Models
1 topic
Basic CRUD
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
from typing import List
app = FastAPI(title="My API", version="1.0")
# Mock DB
fake_db = []
id_counter = 1
class UserCreate(BaseModel):
name: str
email: str
age: int = 18
class UserResponse(BaseModel):
id: int
name: str
email: str
class Config:
from_attributes = True # use orm_mode=True if Pydantic v1
@app.get("/users", response_model=List[UserResponse])
async def list_users():
return fake_db
@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
user = next((u for u in fake_db if u["id"] == user_id), None)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
@app.post("/users", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def create_user(user: UserCreate):
global id_counter
new_user = {
"id": id_counter,
"name": user.name,
"email": user.email,
}
fake_db.append(new_user)
id_counter += 1
return new_user💡 Use response_model to control what fields are returned — never return raw DB objects
⚡ status.HTTP_201_CREATED is clearer than the magic number 201
Introduction: What FastAPI Is, Why It Wins, and When to Use It
2 topics
What FastAPI is and why teams pick it
FastAPI is an async-first Python web framework for building APIs with automatic validation, OpenAPI docs, and high performance through Starlette + Pydantic. Teams choose it because it reduces boilerplate and catches invalid input early.
from fastapi import FastAPI
app = FastAPI(title="Production API")
@app.get("/health")
def health_check():
return {"status": "ok"}FastAPI auto-generates Swagger UI at /docs and ReDoc at /redoc.
Type hints are not decoration here—they drive validation, docs, and editor support.
For teams migrating from Flask, this usually means less manual request parsing and validation code.
FastAPI vs Flask/Django in practical terms
Use FastAPI when API-first development, async I/O, and strict contracts matter. Flask remains simpler for tiny services. Django is stronger for monoliths with admin, ORM, and batteries-included workflows.
# Rule-of-thumb matrix (pseudo-code as comments)
# If you need:
# - API-first + async external calls + OpenAPI docs -> FastAPI
# - Very small sync service or prototype -> Flask
# - Full web app + admin + ORM + templates -> Django
USE_FASTAPI_WHEN = [
"many API endpoints",
"strict input/output schemas",
"high throughput with async I/O",
"multiple client teams relying on API docs",
]
AVOID_FASTAPI_WHEN = [
"you need Django admin-like productivity out of the box",
"team lacks async basics and cannot invest now",
"your app is mostly server-rendered pages",
]FastAPI is not magic speed for CPU-heavy workloads; optimize logic and infrastructure too.
Adoption success depends on consistent schema design and code organization.
Dependency Injection
1 topic
Depends
from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def get_current_user(token: str = Depends(oauth2_scheme)):
user = await verify_token(token)
if not user:
raise HTTPException(status_code=401, detail="Invalid token")
return user
async def require_admin(current_user = Depends(get_current_user)):
if not current_user.is_admin:
raise HTTPException(status_code=403, detail="Forbidden")
return current_user
# Use in routes
@app.delete("/users/{user_id}")
async def delete_user(
user_id: int,
_: User = Depends(require_admin), # Auth enforced
):
await db.delete_user(user_id)
return {"message": "Deleted"}💡 Dependencies compose cleanly — require_admin calls get_current_user automatically
⚡ Reuse dependencies with Depends() to avoid copy-pasting auth checks
Setup, Installation, and Your First Working API
3 topics
Step-by-step project setup
Create an isolated environment, install runtime dependencies, and run with Uvicorn. This is the baseline for every service.
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install --upgrade pip
pip install fastapi uvicorn[standard] pydantic-settings sqlalchemy alembic python-jose[cryptography] passlib[bcrypt]
# optional dev tools
pip install pytest httpx ruff mypyPin dependencies in requirements.txt or use a lockfile (poetry/pdm/pip-tools).
Use pydantic-settings for typed environment config.
Install uvicorn[standard] for faster loop and production-friendly extras.
Minimal Hello World + request/response flow
A minimal app demonstrates endpoint declaration and response serialization. Flow: client request -> router -> function -> JSON response.
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello, FastAPI"}
# Run:
# uvicorn app.main:app --reloadUse async def for I/O-heavy handlers (DB/network/file).
Use --reload only in development.
Test quickly with: curl http://127.0.0.1:8000/
Simple architecture diagram (text-based)
A practical vertical flow for production services including middleware, auth, service layer, and DB.
Keep business rules in services, not in route handlers.
Dependencies are ideal for auth, DB sessions, and tenant context.
Nginx handles TLS termination and buffering in front of Uvicorn.
Core Features and Building a Real CRUD API
2 topics
Path params, query params, and request/response schemas
Use Pydantic models to validate input and shape output. This enforces API contracts and prevents accidental data leaks.
from fastapi import FastAPI, Query
from pydantic import BaseModel, Field
app = FastAPI()
class TodoCreate(BaseModel):
title: str = Field(min_length=3, max_length=120)
done: bool = False
class TodoOut(BaseModel):
id: int
title: str
done: bool
TODOS = [{"id": 1, "title": "Ship API", "done": False}]
@app.get("/todos/{todo_id}", response_model=TodoOut)
def get_todo(todo_id: int):
return next(t for t in TODOS if t["id"] == todo_id)
@app.get("/todos", response_model=list[TodoOut])
def list_todos(limit: int = Query(default=10, ge=1, le=100)):
return TODOS[:limit]
@app.post("/todos", response_model=TodoOut, status_code=201)
def create_todo(payload: TodoCreate):
todo = {"id": len(TODOS) + 1, **payload.model_dump()}
TODOS.append(todo)
return todoresponse_model strips unexpected fields and documents response shape clearly.
Use Field constraints instead of manual if-statements whenever possible.
Validation errors return 422 with detailed location metadata.
Production folder structure and separation of concerns
Organize by responsibility to keep handlers thin and testable: routes, schemas, services, repositories, core config.
app/
main.py
api/
v1/
routes_todos.py
schemas/
todo.py
services/
todo_service.py
repositories/
todo_repo.py
db/
session.py
models.py
core/
config.py
security.py
# app/api/v1/routes_todos.py
from fastapi import APIRouter, Depends
from app.schemas.todo import TodoCreate, TodoOut
from app.services.todo_service import TodoService
router = APIRouter(prefix="/todos", tags=["todos"])
@router.post("", response_model=TodoOut)
def create_todo(payload: TodoCreate, service: TodoService = Depends()):
return service.create(payload)
# app/services/todo_service.py
class TodoService:
def create(self, payload):
# business rules go here (dedupe, audit, etc.)
return {"id": 123, **payload.model_dump()}Route handlers should orchestrate, not implement heavy business logic.
Service layer simplifies unit testing and future refactors.
Add API versioning early (/api/v1) to avoid painful breaking changes.
Advanced Concepts: DI, Middleware, JWT, Async/Sync, and Database CRUD
2 topics
Dependency Injection, middleware, and JWT auth
Use dependencies for cross-cutting concerns and middleware for request lifecycle operations. JWT is a common stateless auth strategy.
from datetime import datetime, timedelta, timezone
from fastapi import FastAPI, Depends, HTTPException, Request
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jose import jwt, JWTError
SECRET = "change-me"
ALGO = "HS256"
security = HTTPBearer()
app = FastAPI()
@app.middleware("http")
async def add_timing_header(request: Request, call_next):
response = await call_next(request)
response.headers["X-Service"] = "fastapi-todo"
return response
def create_access_token(sub: str, minutes: int = 30):
exp = datetime.now(timezone.utc) + timedelta(minutes=minutes)
return jwt.encode({"sub": sub, "exp": exp}, SECRET, algorithm=ALGO)
def get_current_user(creds: HTTPAuthorizationCredentials = Depends(security)):
token = creds.credentials
try:
payload = jwt.decode(token, SECRET, algorithms=[ALGO])
return payload["sub"]
except JWTError:
raise HTTPException(status_code=401, detail="Invalid token")
@app.get("/me")
def me(user_id: str = Depends(get_current_user)):
return {"user_id": user_id}Rotate JWT secrets and keep them in environment variables, not source code.
Prefer short-lived access tokens plus refresh token strategy for real apps.
Middleware is good for correlation IDs, metrics, and security headers.
SQLAlchemy integration with one model and CRUD
Keep DB session lifecycle controlled with a dependency. Map ORM model + Pydantic schemas + service-level operations.
from sqlalchemy import create_engine, String, Boolean
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, sessionmaker, Session
from fastapi import FastAPI, Depends, HTTPException
from pydantic import BaseModel
DATABASE_URL = "sqlite:///./app.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
class Base(DeclarativeBase):
pass
class Todo(Base):
__tablename__ = "todos"
id: Mapped[int] = mapped_column(primary_key=True, index=True)
title: Mapped[str] = mapped_column(String(120), index=True)
done: Mapped[bool] = mapped_column(Boolean, default=False)
Base.metadata.create_all(bind=engine)
class TodoCreate(BaseModel):
title: str
class TodoOut(BaseModel):
id: int
title: str
done: bool
app = FastAPI()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/db/todos", response_model=TodoOut)
def create_todo(payload: TodoCreate, db: Session = Depends(get_db)):
todo = Todo(title=payload.title)
db.add(todo)
db.commit()
db.refresh(todo)
return todo
@app.get("/db/todos/{todo_id}", response_model=TodoOut)
def get_todo(todo_id: int, db: Session = Depends(get_db)):
todo = db.get(Todo, todo_id)
if not todo:
raise HTTPException(404, "Todo not found")
return todoUse Alembic migrations instead of create_all in production.
For PostgreSQL, switch DATABASE_URL and remove SQLite-specific args.
Keep transactions small and explicit to reduce lock contention.
Production Best Practices, Performance, and Common Mistakes
2 topics
Error handling, logging, env config, and Docker/Nginx deployment
Production APIs need predictable errors, structured logs, and containerized runtime. Pair Uvicorn workers behind Nginx for stable edge behavior.
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from pydantic_settings import BaseSettings
import logging
class Settings(BaseSettings):
app_name: str = "todo-api"
debug: bool = False
jwt_secret: str
class Config:
env_file = ".env"
settings = Settings()
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger("api")
app = FastAPI(title=settings.app_name)
@app.exception_handler(ValueError)
async def value_error_handler(_: Request, exc: ValueError):
return JSONResponse(status_code=400, content={"error": str(exc)})
@app.get("/boom")
def boom():
logger.info("Boom endpoint called")
raise ValueError("Invalid business input")
# Dockerfile (minimal)
# FROM python:3.12-slim
# WORKDIR /app
# COPY requirements.txt .
# RUN pip install -r requirements.txt
# COPY . .
# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]Use JSON logs in production and include request_id/user_id for traceability.
Never hardcode secrets; use environment variables or secret managers.
Nginx handles TLS and can protect app workers from slow clients.
Performance, scaling, and mistakes to avoid
FastAPI performs well with async I/O and efficient serialization, but blocking calls inside async routes and poor schema design can erase gains.
from fastapi import FastAPI
import httpx
from fastapi.concurrency import run_in_threadpool
app = FastAPI()
# Good async I/O
@app.get("/external")
async def call_external():
async with httpx.AsyncClient(timeout=5.0) as client:
r = await client.get("https://httpbin.org/get")
return {"status": r.status_code}
# If you must call blocking code, move it off event loop
def blocking_cpu_or_legacy_call():
return sum(range(10_000_000))
@app.get("/blocking-safe")
async def blocking_safe():
result = await run_in_threadpool(blocking_cpu_or_legacy_call)
return {"result": result}
# Example curl:
# curl -H "Authorization: Bearer <token>" http://localhost:8000/meDo not call synchronous DB/network clients directly inside async handlers.
Add caching (Redis) for hot read endpoints to reduce DB load.
Apply rate limiting at gateway or app layer for abusive traffic patterns.
Avoid mutable global state for request-specific data; use dependencies/context.
Related Cheat Sheets
More hands-on references connected to this topic.
Essential PyTorch patterns for tensor ops, autograd, model building, training loops, and deployment.
shared topics, same difficulty
A practical cheat sheet for loading, cleaning, transforming, analyzing, and exporting tabular data with Pandas, plus how it fits with PyTorch workflows.
shared topics
shared topics
Python syntax, data structures, comprehensions, OOP, error handling, and standard library essentials.
shared topics
Related Articles
Background reading and deeper explanations for this sheet.
Agent Routing vs Model Routing: Real Production Patterns, Tradeoffs, and Implementation Guide
Choosing between agent routing and model routing can make or break your AI product’s cost, speed, and reliability. In this practical guide, you’ll learn what each pattern is, when to use it, and how production teams combine both to ship robust systems. We’ll walk through real examples, architecture patterns, and code you can adapt immediately.
keyword overlap
AI Product Pricing Strategies: How to Monetize for Growth, Margin, and Customer Trust
Pricing an AI product is not just a finance decision—it shapes adoption, retention, margins, and user trust. In this practical guide, you’ll learn how to choose the right pricing model, align price with value and costs, and avoid common mistakes using real-world examples and implementation frameworks.
keyword overlap
Build an Agentic App with FastAPI and Azure AI Foundry: A Practical Beginner-to-Pro Guide
Learn how to design, build, and deploy an agentic application using FastAPI and Azure AI Foundry with practical, real-world examples. This guide walks you from architecture and setup to tool-calling, memory, observability, and production deployment patterns.
keyword overlap
Building Autonomous Agents Without Overengineering: A Practical Guide for Real-World Teams
Autonomous agents can automate meaningful work, but many projects fail because teams overbuild too early. This practical guide shows you how to design, ship, and improve useful agents with simple patterns, clear guardrails, and real-world examples—without turning your stack into a science project.
keyword overlap