FastAPI
01 / 03

Path Operations & Pydantic

Path Operations & Pydantic

App Setup & Routing

from fastapi import FastAPI, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI(
    title="My API",
    description="FastAPI example",
    version="1.0.0",
    docs_url="/docs",       # Swagger UI
    redoc_url="/redoc",     # ReDoc
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://myapp.com"],
    allow_methods=["*"],
    allow_headers=["*"],
    allow_credentials=True,
)

# Path operations
@app.get("/")
def root():
    return {"message": "Hello World"}

@app.get("/items/{item_id}")
def get_item(item_id: int):            # auto-validates int
    return {"id": item_id}

# Query parameters
@app.get("/users")
def list_users(
    page: int = 1,
    limit: int = 10,
    search: str | None = None,         # optional
    active: bool = True,
):
    return {"page": page, "limit": limit, "search": search}

# Multiple path params
@app.get("/users/{user_id}/posts/{post_id}")
def get_user_post(user_id: int, post_id: int):
    return {"user_id": user_id, "post_id": post_id}

# Response status codes
@app.post("/users", status_code=status.HTTP_201_CREATED)
def create_user(user: UserCreate):
    return {"id": 1, **user.model_dump()}

@app.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_user(user_id: int):
    pass

# HTTP errors
def get_or_404(item_id: int):
    item = db.get(item_id)
    if not item:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Item {item_id} not found",
        )
    return item

Pydantic Models

from pydantic import BaseModel, Field, EmailStr, field_validator, model_validator
from typing import Optional
from datetime import datetime
from enum import Enum

class UserRole(str, Enum):
    admin = "admin"
    user = "user"

class UserBase(BaseModel):
    email: EmailStr
    name: str = Field(..., min_length=2, max_length=100)
    role: UserRole = UserRole.user

class UserCreate(UserBase):
    password: str = Field(..., min_length=8)

    @field_validator("password")
    @classmethod
    def password_must_have_uppercase(cls, v: str) -> str:
        if not any(c.isupper() for c in v):
            raise ValueError("Password must contain uppercase letter")
        return v

class UserResponse(UserBase):
    id: int
    created_at: datetime

    model_config = {"from_attributes": True}  # enables ORM mode

class UserUpdate(BaseModel):
    name: Optional[str] = Field(None, min_length=2)
    bio: Optional[str] = None

# Nested models
class Address(BaseModel):
    street: str
    city: str
    country: str = "US"

class UserWithAddress(UserBase):
    address: Optional[Address] = None
    tags: list[str] = []
    metadata: dict[str, str] = {}

# Usage
user = UserCreate(email="user@example.com", name="Alice", password="Password1")
print(user.model_dump())
print(user.model_dump_json())
user_dict = {"email": "a@b.com", "name": "Bob", "password": "StrongPass1"}
user = UserCreate.model_validate(user_dict)

Request Body & Response Models

from fastapi import FastAPI, Body
from fastapi.responses import JSONResponse, StreamingResponse

# Request body
@app.post("/users", response_model=UserResponse, status_code=201)
def create_user(user: UserCreate) -> UserResponse:
    # response_model filters output — only UserResponse fields returned
    db_user = create_in_db(user)
    return db_user

# Multiple body params
@app.put("/users/{user_id}")
def update_user(
    user_id: int,
    user: UserUpdate,
    reason: str = Body(..., embed=True),  # extra body field
):
    return {"id": user_id, "reason": reason, **user.model_dump(exclude_none=True)}

# response_model_exclude / include
@app.get("/users/me", response_model=UserResponse, response_model_exclude={"password"})
def get_me():
    ...

# List response
@app.get("/users", response_model=list[UserResponse])
def list_users():
    return db.get_all_users()

# Custom response
@app.get("/export")
def export():
    def generate():
        yield "col1,col2
"
        yield "a,b
"
    return StreamingResponse(generate(), media_type="text/csv",
                             headers={"Content-Disposition": "attachment; filename=data.csv"})

Routers (APIRouter)

# routers/users.py
from fastapi import APIRouter

router = APIRouter(prefix="/users", tags=["users"])

@router.get("/")
def list_users():
    return []

@router.get("/{user_id}")
def get_user(user_id: int):
    return {"id": user_id}

# main.py
from routers import users, posts, auth

app.include_router(auth.router, prefix="/auth", tags=["auth"])
app.include_router(users.router)
app.include_router(posts.router, prefix="/api/v1")

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free