Backend — FastAPI
FastAPI Framework
Modern async Python APIs with automatic validation, serialization, and Swagger docs from type hints. One of the fastest Python frameworks — built for APIs and ML model serving.
Async / ASGIPython 3.10+Auto Swagger
ASGIServer
PydanticValidation
OpenAPIAuto Docs
AsyncNative
High Perf
High-Performance APIs with Zero Boilerplate
FastAPI is built on Starlette (ASGI) and Pydantic (validation). Write standard Python with type hints —
FastAPI handles validation, serialization, error messages, and generates interactive API docs automatically.
Request
JSON / Form / Query
→
Pydantic
Validate & Parse
→
Your Function
Clean Python objects
Blazing Performance
On par with Node.js and Go. Native async/await via Starlette ASGI — handles thousands of concurrent requests.
Automatic Docs
Swagger UI at
/docs and ReDoc at /redoc — generated from your code, zero extra work.Type Safety
Python type hints power validation, serialization, and IDE autocomplete. Catch bugs at definition time.
Dependency Injection
A clean DI system for sharing DB sessions, auth checks, and common logic across routes without repetition.
Standards-Based
Fully OpenAPI 3.0 and JSON Schema compliant. Auto-generate client SDKs in any language from your API.
ML Model Serving
The go-to choice for wrapping ML models as APIs. Used by Netflix, Uber, Microsoft, and countless AI teams.
2018
Year Released
Created by Sebastián Ramírez (tiangolo)
ASGI
Server Type
Async — vs WSGI (Flask/Django sync)
3rd
Most Starred
Python web framework on GitHub
~3×
Faster than Flask
Benchmarks on async I/O workloads
Quick Start — Up and Running in Minutes
Install FastAPI, Uvicorn (ASGI server), and write your first typed endpoint. The docs auto-appear the moment the server starts.
01
Install FastAPI & Uvicorn
Uvicorn is the ASGI server that runs FastAPI. The
[standard] extra adds websocket and performance libraries.bash
pip install fastapi uvicorn[standard]
# For database support:
pip install sqlalchemy databases asyncpg
# For JWT auth:
pip install python-jose[cryptography] passlib[bcrypt]
02
Your First FastAPI App
Notice how type hints do the work —
item_id: int validates and converts automatically. No extra code needed.main.py
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI(title="My API", version="1.0.0")
class Item(BaseModel):
name: str
price: float
stock: int = 0
@app.get("/")
def root():
return {"message": "Hello World"}
@app.get("/items/{item_id}")
def get_item(item_id: int): # int validated automatically
return {"id": item_id}
@app.post("/items", status_code=201)
def create_item(item: Item): # body auto-parsed & validated
return item
03
Run the Server
The
--reload flag restarts on every file save. Visit /docs for instant interactive API documentation.bash
# Development — auto-reload on save
uvicorn main:app --reload
# Production — multiple workers
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
# Then open:
# http://localhost:8000/docs ← Swagger UI
# http://localhost:8000/redoc ← ReDoc
# http://localhost:8000/openapi.json ← Raw schema
04
Recommended Project Structure
For production apps, split into routers, schemas, models, and database modules.
myapi/
├── main.py # app instance + routers
├── .env # secrets (DB URL, JWT secret)
├── database.py # engine, session, Base
├── models.py # SQLAlchemy ORM models
├── schemas.py # Pydantic request/response schemas
├── dependencies.py # get_db, get_current_user
└── routers/
├── users.py # /users endpoints
├── items.py # /items endpoints
└── auth.py # /auth/login, /auth/refresh
Routing, Path & Query Parameters
FastAPI's routing is decorator-based like Flask, but type hints do all the validation and conversion work. No manual parsing needed.
01
Path Parameters with Validation
Declare path params in the route string and function signature. FastAPI validates and converts the type automatically — wrong type = 422 Unprocessable Entity.
python
from fastapi import FastAPI, Path
from enum import Enum
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
vgg = "vgg"
# Simple path param with type validation
@app.get("/users/{user_id}")
def get_user(user_id: int):
return {"id": user_id}
# Enum param — only allows defined values
@app.get("/models/{model_name}")
def get_model(model_name: ModelName):
return {"model": model_name, "value": model_name.value}
# Path() adds constraints and docs metadata
@app.get("/items/{item_id}")
def get_item(item_id: int = Path(gt=0, description="Item ID must be positive")):
return {"id": item_id}
02
Query Parameters & Optional Fields
Any function parameter not in the path string becomes a query parameter. Use
Optional or a default of None to make it optional.python
from typing import Optional
from fastapi import Query
# Required query param: GET /items?category=books
@app.get("/search")
def search(q: str):
return {"query": q}
# Optional with defaults: GET /items?skip=0&limit=20&active=true
@app.get("/items")
def list_items(
skip: int = 0,
limit: int = 20,
active: Optional[bool] = None,
q: Optional[str] = Query(None, min_length=3, max_length=50)
):
return {"skip": skip, "limit": limit, "active": active, "q": q}
03
APIRouter — Organizing Routes
Split routes across files using
APIRouter, then include them in the main app. Equivalent to Flask Blueprints.routers/users.py
from fastapi import APIRouter
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/")
def list_users(): # → GET /users/
return []
@router.get("/{user_id}")
def get_user(user_id: int): # → GET /users/{user_id}
return {"id": user_id}
# In main.py:
# from routers import users
# app.include_router(users.router)
Pydantic — Validation & Serialization
Pydantic models define the shape of your request bodies and responses. They validate incoming data, throw clear error messages for bad input, and handle serialization to/from JSON automatically.
01
Request & Response Schemas
Define separate schemas for creating vs. reading objects. Use
response_model to strip fields you don't want to expose (like passwords).schemas.py
from pydantic import BaseModel, EmailStr, Field
from typing import Optional
from datetime import datetime
# Schema for creating a user (incoming request)
class UserCreate(BaseModel):
username: str = Field(min_length=3, max_length=50)
email: EmailStr # validates email format
password: str = Field(min_length=8)
# Schema for reading a user (response — no password!)
class UserRead(BaseModel):
id: int
username: str
email: str
created: datetime
class Config:
from_attributes = True # allow ORM model → schema conversion
# Use response_model to filter the output automatically
@app.post("/users", response_model=UserRead, status_code=201)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
# password never appears in the response — UserRead excludes it
...
02
Field Validation & Constraints
Use
Field() for constraints and metadata. Validators run custom logic. FastAPI surfaces all validation errors as a 422 response with clear messages.python
from pydantic import BaseModel, Field, field_validator
class Product(BaseModel):
name: str = Field(min_length=1, max_length=200, description="Product name")
price: float = Field(gt=0, description="Must be positive")
discount: float = Field(default=0.0, ge=0, le=1) # 0% to 100%
tags: list[str] = []
@field_validator('name')
@classmethod
def name_must_not_be_blank(cls, v: str) -> str:
if not v.strip():
raise ValueError('name cannot be blank')
return v.strip()
03
Nested Models & Lists
Pydantic models compose naturally. FastAPI handles nested validation and generates accurate JSON Schema for the docs.
python
class Address(BaseModel):
street: str
city: str
zip: str
class UserProfile(BaseModel):
name: str
address: Address # nested model
friends: list[str] = [] # list of strings
# FastAPI accepts + validates this JSON automatically:
# {
# "name": "Omer",
# "address": { "street": "MG Road", "city": "Hyderabad", "zip": "500001" },
# "friends": ["Alice", "Bob"]
# }
Async & Await — Native Concurrency
FastAPI is built on ASGI and handles async natively. Async routes let the server handle other requests while waiting for I/O — database queries, HTTP calls, file reads — instead of blocking the whole process.
01
async def vs def Routes
Use
async def when your route does I/O (DB query, HTTP request, file read). Use regular def for CPU-bound work — FastAPI runs these in a thread pool automatically.python
import httpx
# Async route — awaits I/O without blocking the server
@app.get("/weather/{city}")
async def get_weather(city: str):
async with httpx.AsyncClient() as client:
res = await client.get(f"https://api.weather.com/{city}")
return res.json()
# Regular def — runs in threadpool, won't block async loop
@app.get("/compute")
def heavy_compute():
result = some_cpu_intensive_function()
return {"result": result}
02
Background Tasks
Run work after returning the response — sending emails, writing logs, processing uploads — without making the client wait.
python
from fastapi import BackgroundTasks
def send_welcome_email(email: str):
# Runs after the response is sent — client doesn't wait
send_email(to=email, subject="Welcome!", body="Thanks for signing up.")
@app.post("/register")
async def register(user: UserCreate, background_tasks: BackgroundTasks):
db_user = create_user_in_db(user)
background_tasks.add_task(send_welcome_email, user.email)
return {"message": "Registered! Welcome email sent."}
03
Startup & Shutdown Events
Run code when the application starts or shuts down — connect to databases, load ML models, clean up resources.
python
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup — runs once when server starts
print("Loading ML model...")
app.state.model = load_ml_model()
yield
# Shutdown — runs when server stops
print("Cleaning up...")
app.state.model = None
app = FastAPI(lifespan=lifespan)
FastAPI is the go-to choice for serving ML models because async I/O keeps the server responsive while model inference runs. Libraries like Hugging Face and LangChain have native FastAPI integration examples.
Dependency Injection
FastAPI's DI system is one of its best features. Declare a function as a dependency with
Depends() — FastAPI calls it, resolves its own dependencies, and injects the result into your route. Clean, testable, composable.01
Basic Dependencies
Share common query parameters, pagination logic, or any computed value across multiple routes.
python
from fastapi import Depends
# Reusable pagination dependency
def paginate(page: int = 1, size: int = 20) -> dict:
return {"skip": (page - 1) * size, "limit": size}
# Inject into any route — no code duplication
@app.get("/users")
def list_users(p: dict = Depends(paginate)):
return db.query(User).offset(p["skip"]).limit(p["limit"]).all()
@app.get("/items")
def list_items(p: dict = Depends(paginate)):
return db.query(Item).offset(p["skip"]).limit(p["limit"]).all()
02
Database Session Dependency
The most common dependency pattern — open a DB session per request, yield it, then close it whether or not an exception occurred.
dependencies.py
from database import SessionLocal
from sqlalchemy.orm import Session
def get_db() -> Session:
db = SessionLocal()
try:
yield db # inject into the route
finally:
db.close() # always close, even on errors
# Every route gets its own session — no leaks
@app.get("/users/{uid}")
def get_user(uid: int, db: Session = Depends(get_db)):
return db.query(User).filter(User.id == uid).first()
03
Router-Level Dependencies
Apply a dependency to all routes in a router — e.g., require authentication for every endpoint under
/admin.python
from fastapi import APIRouter, Depends
from dependencies import require_admin
# Every route in this router requires admin auth
admin_router = APIRouter(
prefix="/admin",
tags=["admin"],
dependencies=[Depends(require_admin)]
)
@admin_router.get("/users")
def list_all_users(db: Session = Depends(get_db)):
return db.query(User).all() # require_admin runs automatically
JWT Authentication
FastAPI has no built-in auth, but its DI system makes JWT implementation clean and reusable. Protect any route by adding a single
Depends(get_current_user).01
Password Hashing & Token Generation
Use
passlib for password hashing and python-jose for JWT creation and verification.auth.py
from datetime import datetime, timedelta
from jose import jwt, JWTError
from passlib.context import CryptContext
SECRET_KEY = "your-secret-key-from-env"
ALGORITHM = "HS256"
TOKEN_EXP = 30 # minutes
pwd_context = CryptContext(schemes=["bcrypt"])
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain: str, hashed: str) -> bool:
return pwd_context.verify(plain, hashed)
def create_access_token(data: dict) -> str:
payload = data.copy()
payload["exp"] = datetime.utcnow() + timedelta(minutes=TOKEN_EXP)
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
02
Login Endpoint & Token Response
OAuth2PasswordRequestForm is a built-in FastAPI form that accepts
username and password — standard OAuth2 format.routers/auth.py
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
auth_router = APIRouter(tags=["auth"])
@auth_router.post("/login")
def login(
form: OAuth2PasswordRequestForm = Depends(),
db: Session = Depends(get_db)
):
user = db.query(User).filter(User.email == form.username).first()
if not user or not verify_password(form.password, user.password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials"
)
token = create_access_token({"sub": str(user.id)})
return {"access_token": token, "token_type": "bearer"}
03
Protected Routes with get_current_user
A single dependency that reads and validates the JWT from the Authorization header. Inject it into any route to protect it.
dependencies.py
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")
def get_current_user(
token: str = Depends(oauth2_scheme),
db: Session = Depends(get_db)
) -> User:
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
user_id = int(payload.get("sub"))
except (JWTError, ValueError):
raise HTTPException(status_code=401, detail="Invalid token")
user = db.query(User).get(user_id)
if not user:
raise HTTPException(status_code=401, detail="User not found")
return user
# Protect any route with one line:
@app.get("/me", response_model=UserRead)
def get_me(current_user: User = Depends(get_current_user)):
return current_user
Database with SQLAlchemy
FastAPI works with any database. SQLAlchemy is the standard choice. Define models in
models.py, Pydantic schemas in schemas.py, and keep them separate — a key FastAPI pattern.01
Database Setup
Create the engine, session factory, and declarative base. All models inherit from
Base.database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os
DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:///./dev.db")
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Create all tables (use Alembic for production migrations)
# Base.metadata.create_all(bind=engine)
02
SQLAlchemy Models vs Pydantic Schemas
Keep these separate. SQLAlchemy models talk to the DB. Pydantic schemas validate API input/output. Use
from_attributes = True to convert between them.models.py
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from database import Base
from datetime import datetime
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, nullable=False)
password = Column(String, nullable=False)
created = Column(DateTime, default=datetime.utcnow)
posts = relationship("Post", back_populates="author")
03
CRUD in Routes
Combine the DB session dependency with Pydantic schemas for clean, type-safe database routes.
routers/users.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/", response_model=list[UserRead])
def list_users(skip: int = 0, limit: int = 20, db: Session = Depends(get_db)):
return db.query(User).offset(skip).limit(limit).all()
@router.post("/", response_model=UserRead, status_code=201)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
if db.query(User).filter(User.email == user.email).first():
raise HTTPException(400, "Email already registered")
db_user = User(email=user.email, password=hash_password(user.password))
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
Automatic Interactive API Documentation
FastAPI generates full API documentation from your code — no YAML files, no separate doc writing. Every endpoint, parameter, schema, and response is documented automatically.
01
Swagger UI at /docs
Interactive docs where you can test every endpoint directly in the browser — fill in parameters, send requests, see responses. Auto-generated from your route definitions.
bash
# Available automatically when server runs:
http://localhost:8000/docs # Swagger UI — interactive
http://localhost:8000/redoc # ReDoc — cleaner, good for sharing
http://localhost:8000/openapi.json # Raw OpenAPI 3.0 schema
02
Enriching the Docs
Add descriptions, examples, and metadata to your API, routes, and schemas — all appear in the generated docs.
python
# App-level metadata
app = FastAPI(
title="My API",
description="A full-featured API built with FastAPI",
version="1.0.0",
contact={"name": "Omer", "email": "omer@example.com"},
)
# Route-level metadata
@app.get("/items/{item_id}",
summary="Get item by ID",
description="Returns a single item. Raises 404 if not found.",
response_description="The item object",
tags=["items"])
def get_item(item_id: int):
...
# Schema-level examples
class Item(BaseModel):
name: str = Field(example="Laptop")
price: float = Field(example=999.99)
03
Disable Docs in Production (Optional)
You may want to disable public docs in production for security or to avoid exposing your API structure.
python
import os
# Disable docs in production, keep in development
app = FastAPI(
docs_url="/docs" if os.getenv("ENV") != "production" else None,
redoc_url="/redoc" if os.getenv("ENV") != "production" else None,
)
The OpenAPI schema at
/openapi.json can be fed into tools like openapi-generator to auto-generate TypeScript, Kotlin, or Swift client SDKs — zero manual API client code.
FastAPI Quick-Reference Cheatsheet
Essential imports, patterns, and CLI commands in one place.
python — essential imports
from fastapi import (
FastAPI, APIRouter, Depends, HTTPException, status,
Path, Query, Body, Header, BackgroundTasks
)
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel, Field, EmailStr, field_validator
from sqlalchemy.orm import Session
from typing import Optional, Annotated
from jose import jwt, JWTError
from passlib.context import CryptContext
| Pattern / Command | What It Does |
|---|---|
FastAPI(title, version) | Create the app instance with metadata |
@app.get / post / put / delete / patch | Register an HTTP route |
response_model=Schema | Filter and validate the response shape |
status_code=201 | Set custom HTTP status on success |
Depends(func) | Inject a dependency into a route |
HTTPException(status_code, detail) | Return an HTTP error response |
Path(gt=0) | Add constraints to a path parameter |
Query(None, min_length=3) | Add constraints to a query parameter |
Field(min_length=1, example="x") | Add constraints and docs to a Pydantic field |
app.include_router(router, prefix="/v1") | Mount an APIRouter into the app |
uvicorn main:app --reload | Start dev server with auto-reload |
uvicorn main:app --workers 4 | Start production server with 4 workers |
Test Your FastAPI Knowledge
5 questions covering the key concepts — type hints, Pydantic, async, DI, and auto docs.
1If you declare a route parameter as
item_id: int but the client sends /items/abc, what does FastAPI return?2What is the purpose of
response_model=UserRead on a route that returns a SQLAlchemy User object?3Which ASGI server is the standard choice for running FastAPI in production?
4In the
get_db() dependency, why does it use yield instead of return?5Where does FastAPI automatically generate interactive API documentation?
6Pydantic models in FastAPI primarily provide…
7FastAPI dependency injection uses…
8When should you use
async def route handlers?9OpenAPI schema in FastAPI is…
10FastAPI vs Flask — common trade-off?
—
Keep practicing!