Build high-performance Python APIs from scratch. Every import explained, every concept covered, every pattern production-ready.
FastAPI is a modern, high-performance Python web framework for building APIs. It's built on two core technologies: Starlette (for the async web layer) and Pydantic (for data validation). It's one of the fastest Python frameworks available — comparable to Node.js and Go in benchmarks.
Automatic Swagger docs • Type hints-based validation • Async support built-in • Dependency injection system • OAuth2 & JWT support • Generates OpenAPI schema automatically
pip install fastapi
pip install uvicorn[standard]
| Package | What It Does |
|---|---|
fastapi |
The framework itself — provides routing, validation, docs, dependency injection |
uvicorn |
An ASGI server that runs your FastAPI app. ASGI (Asynchronous Server Gateway Interface) is the async successor to WSGI. The [standard] extra installs performance deps like uvloop and httptools |
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Hello, World!"}
| Import | Explanation |
|---|---|
FastAPI |
The main class of the framework. app = FastAPI() creates an ASGI application instance. This class inherits from Starlette's Starlette class and adds validation, documentation, and dependency injection on top. |
uvicorn main:app --reload
| Part | Meaning |
|---|---|
main | Your Python filename (main.py) |
app | The FastAPI instance variable |
--reload | Auto-restart on code changes (development only) |
/ with method GET/, FastAPI calls read_root()
Swagger UI: http://127.0.0.1:8000/docs
ReDoc: http://127.0.0.1:8000/redoc
OpenAPI JSON: http://127.0.0.1:8000/openapi.json
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id}
{item_id} in the path becomes a function parameter. The type hint int tells FastAPI to validate and convert the path parameter. If someone sends /items/abc, FastAPI returns a 422 Validation Error automatically — powered by Pydantic under the hood.
If you have both /users/me and /users/{user_id}, put /users/me FIRST. Otherwise /users/me would match /users/{user_id} with user_id="me".
from enum import Enum
from fastapi import FastAPI
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
app = FastAPI()
@app.get("/models/{model_name}")
def get_model(model_name: ModelName):
return {"model": model_name.value}
| Import | Explanation |
|---|---|
Enum from enum |
Python's built-in enumeration. Inheriting from both str and Enum makes values treated as strings in the OpenAPI schema — shows as a dropdown in Swagger UI. |
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
if q:
return {"item_id": item_id, "q": q}
return {"item_id": item_id}
Any function parameter NOT in the path is automatically a query parameter. URL example: /items/5?q=hello
| Import | Explanation |
|---|---|
Union from typing |
Declares that q can be str OR None. Setting default to None makes it optional. In Python 3.10+ you can use str | None instead. |
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Union
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
@app.post("/items/")
def create_item(item: Item):
item_dict = item.model_dump()
if item.tax:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict
| Import | Explanation |
|---|---|
BaseModel from pydantic |
The foundation of Pydantic's data validation. Every field with type hints becomes a validated field. Pydantic parses incoming JSON, validates types, coerces where possible ("25.5" → 25.5), and raises clear errors when validation fails. |
name is missing → 422 errorprice is "25.5" → Pydantic coerces it to 25.5price is "hello" → 422 error with details
item.model_dump() — Converts model → dict
item.model_dump_json() — Converts → JSON string
Item.model_validate(data) — Creates instance from dict
from pydantic import BaseModel, Field
from fastapi import FastAPI, Query, Path
app = FastAPI()
class Item(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
price: float = Field(..., gt=0, description="Must be > zero")
tax: float = Field(default=0.0, ge=0, le=100)
@app.get("/items/")
def read_items(
q: str = Query(default=None, min_length=3, max_length=50),
item_id: int = Path(..., ge=1, le=1000),
):
return {"q": q, "item_id": item_id}
| Import | Explanation |
|---|---|
Field from pydantic |
Extra validation for model fields. ... (Ellipsis) = required. Params: gt, ge, lt, le for numbers; min_length, max_length, pattern for strings. |
Query from fastapi |
Validation for query parameters. Works like Field but for URL params like ?q=hello. Also supports List[str] for multi-value params. |
Path from fastapi |
Validation for path parameters. Since path params are always required (part of the URL), ... is standard. |
class UserIn(BaseModel):
username: str
password: str
email: str
class UserOut(BaseModel):
username: str
email: str
@app.post("/users/", response_model=UserOut)
def create_user(user: UserIn):
return user # password is automatically filtered out!
response_model=UserOut filters the response through UserOut. Fields NOT in UserOut (like password) are stripped. You never accidentally leak sensitive data.
from fastapi import FastAPI, status
app = FastAPI()
@app.post("/items/", status_code=status.HTTP_201_CREATED)
def create_item(name: str):
return {"name": name}
| Import | Explanation |
|---|---|
status from fastapi |
HTTP status code constants. Using status.HTTP_201_CREATED instead of 201 → readable code + IDE autocomplete. Common: 200 OK, 201 Created, 204 No Content, 400 Bad Request, 404 Not Found, 422 Validation Error |
from fastapi import FastAPI, File, UploadFile, Form
app = FastAPI()
@app.post("/login/")
def login(username: str = Form(...), password: str = Form(...)):
return {"username": username}
@app.post("/upload/")
async def upload(file: UploadFile = File(...)):
contents = await file.read()
return {"filename": file.filename, "size": len(contents)}
| Import | Explanation |
|---|---|
Form | Reads from form data (application/x-www-form-urlencoded) instead of JSON. Required for HTML form submissions. |
File | Declares a parameter as file upload. File content received as bytes. |
UploadFile | Wraps uploaded file with attributes: .filename, .content_type, await .read(), await .seek(0). Small files in memory, large files on disk. Requires pip install python-multipart. |
from fastapi import FastAPI, HTTPException, status
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(item_id: int):
if item_id == 0:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Item not found",
headers={"X-Error": "custom header"},
)
return {"item_id": item_id}
| Import | Explanation |
|---|---|
HTTPException |
The standard way to return HTTP errors. When raised, FastAPI stops execution and returns the status code + detail as JSON. You can also add custom headers. |
This is one of FastAPI's most powerful features.
from fastapi import FastAPI, Depends
app = FastAPI()
def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
def read_items(commons: dict = Depends(common_parameters)):
return commons
| Import | Explanation |
|---|---|
Depends from fastapi |
The core of dependency injection. When FastAPI sees Depends(func), it: (1) Calls func with appropriate request parameters, (2) Passes the return value to your endpoint. Dependencies can have sub-dependencies → chains. |
def get_db():
db = SessionLocal()
try:
yield db # Give session to endpoint
finally:
db.close() # ALWAYS close, even on errors
@app.get("/items/")
def get_items(db: Session = Depends(get_db)):
return db.query(Item).all()
Code before yield runs before the endpoint. Code after yield (in finally) runs after the response is sent — perfect for DB session cleanup.
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def get_current_user(token: str = Depends(oauth2_scheme)):
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
# ... validate and return user
| Import | Explanation |
|---|---|
OAuth2PasswordBearer | Extracts Bearer token from Authorization header. tokenUrl tells Swagger UI where to send login credentials. Adds 🔒 icon to endpoints. |
OAuth2PasswordRequestForm | Parses standard OAuth2 login form (username + password as form data). Powers Swagger UI's "Authorize" button. |
jwt from jose | Creates & decodes JSON Web Tokens. jwt.encode() creates, jwt.decode() verifies. Install: pip install python-jose[cryptography] |
CryptContext from passlib | Password hashing with bcrypt. Intentionally slow (~100ms) to prevent brute force. Install: pip install passlib[bcrypt] |
import time
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_methods=["*"],
allow_headers=["*"],
)
@app.middleware("http")
async def add_timing(request: Request, call_next):
start = time.time()
response = await call_next(request)
response.headers["X-Time"] = str(time.time() - start)
return response
| Import | Explanation |
|---|---|
CORSMiddleware | Handles Cross-Origin Resource Sharing. Without this, your frontend on localhost:3000 can't call your API on localhost:8000 — browsers block cross-origin requests by default. |
call_next | Passes the request to the next middleware or endpoint. Request → Middleware (before) → Endpoint → Middleware (after) → Response. |
from fastapi import FastAPI, BackgroundTasks
app = FastAPI()
def send_email(email: str, message: str):
# Runs AFTER response is sent
print(f"Sent to {email}: {message}")
@app.post("/notify/{email}")
async def notify(email: str, bg: BackgroundTasks):
bg.add_task(send_email, email, "Welcome!")
return {"message": "Notification queued"}
| Import | Explanation |
|---|---|
BackgroundTasks | Collects tasks to run after the response is sent. Client doesn't wait. Ideal for emails, logging, analytics. For heavy work, use Celery instead. |
from fastapi import APIRouter
router = APIRouter(prefix="/items", tags=["items"])
@router.get("/")
def read_items():
return [{"name": "Item1"}]
from fastapi import FastAPI
from routers import items
app = FastAPI()
app.include_router(items.router)
| Import | Explanation |
|---|---|
APIRouter | Works like FastAPI() for defining routes but designed to be included into the main app. Like Django's per-app urls.py or Flask's Blueprint. Key params: prefix, tags, dependencies. |
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker, declarative_base, Session
from pydantic import BaseModel, ConfigDict
from fastapi import Depends
engine = create_engine("sqlite:///./app.db")
SessionLocal = sessionmaker(bind=engine)
Base = declarative_base()
# ORM Model (database table)
class ItemDB(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True)
name = Column(String)
# Pydantic Schema (API validation)
class ItemResponse(BaseModel):
id: int
name: str
model_config = ConfigDict(from_attributes=True)
| Import | Explanation |
|---|---|
create_engine | Creates DB connection engine with connection pool. URL format: sqlite:///./app.db or postgresql://user:pass@host/db |
sessionmaker | Factory for Session objects. A Session tracks changes and writes on commit(). |
declarative_base | Base class for ORM models. Inheriting from Base maps class → table. |
ConfigDict(from_attributes=True) | Tells Pydantic to read ORM attributes (item.name) instead of dict keys. Required when returning SQLAlchemy objects. |
# Async — use when calling await-compatible libraries
@app.get("/async")
async def async_endpoint():
async with httpx.AsyncClient() as client:
resp = await client.get("https://api.example.com")
return resp.json()
# Sync — use with blocking I/O (runs in thread pool automatically)
@app.get("/sync")
def sync_endpoint():
time.sleep(1) # Won't freeze the server
return {"done": True}
async def — when using await (httpx, aiofiles, async DB drivers).
def (no async) — when using synchronous libs (requests, standard file I/O). FastAPI runs these in a thread pool automatically.
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
app = FastAPI()
@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
await ws.accept()
try:
while True:
data = await ws.receive_text()
await ws.send_text(f"Echo: {data}")
except WebSocketDisconnect:
print("Client disconnected")
| Import | Explanation |
|---|---|
WebSocket | Represents a WebSocket connection. Methods: accept(), send_text(), send_json(), receive_text(), close(). |
WebSocketDisconnect | Exception raised when client disconnects. Catch it to handle cleanup gracefully. |
# ═══ CORE ═══
from fastapi import FastAPI # Main application class
from fastapi import APIRouter # Sub-router for organizing routes
from fastapi import Depends # Dependency injection
from fastapi import HTTPException # Raise HTTP errors
from fastapi import Request # Access raw request object
from fastapi import status # HTTP status code constants
# ═══ PARAMETERS ═══
from fastapi import Query # Query parameter validation
from fastapi import Path # Path parameter validation
from fastapi import Body # Request body validation
from fastapi import Header # Header extraction
from fastapi import Cookie # Cookie extraction
from fastapi import Form # Form data extraction
from fastapi import File # File upload (bytes)
from fastapi import UploadFile # File upload (with metadata)
# ═══ RESPONSES ═══
from fastapi.responses import JSONResponse
from fastapi.responses import HTMLResponse
from fastapi.responses import RedirectResponse
from fastapi.responses import StreamingResponse
from fastapi.responses import FileResponse
# ═══ SECURITY ═══
from fastapi.security import OAuth2PasswordBearer
from fastapi.security import OAuth2PasswordRequestForm
# ═══ MIDDLEWARE ═══
from fastapi.middleware.cors import CORSMiddleware
# ═══ BACKGROUND & WEBSOCKET ═══
from fastapi import BackgroundTasks
from fastapi import WebSocket, WebSocketDisconnect
# ═══ TESTING ═══
from fastapi.testclient import TestClient
# ═══ PYDANTIC ═══
from pydantic import BaseModel # Base for data models
from pydantic import Field # Field-level validation
from pydantic import ConfigDict # Model configuration
from pydantic import field_validator # Custom field validators
from pydantic import model_validator # Model-level validators
# ═══ PYTHON TYPING ═══
from typing import Union # str | None
from typing import List, Dict, Optional
from typing import Annotated # Modern Query/Path pattern