CRUD Application Overview#
This page describes the architecture of a CRUD API built with FastAPI, SQLAlchemy, Pydantic, and Alembic, showing how routes, database sessions, ORM models, and schemas fit together in a clean, layered structure.
Big picture#
graph TD
Client[HTTP Client] --> Routes[FastAPI Routes\nmain.py]
Routes -->|Depends get_db| DI[Dependency Injection\ndatabase.py]
Routes -->|validate input| Schemas[Pydantic Schemas\nschemas.py]
Routes -->|call CRUD functions| CRUD[CRUD Layer\ncrud.py]
DI -->|Session| CRUD
CRUD -->|ORM queries| Models[SQLAlchemy Models\nmodels.py]
Models -->|SQL| DB[(Database)]
Alembic[Alembic Migrations\ndb-migration/] -->|manage schema| DB
style Routes fill:#9cf,stroke:#333
style CRUD fill:#9f9,stroke:#333
style Models fill:#fc9,stroke:#333
style DB fill:#f9c,stroke:#333
A CRUD (Create, Read, Update, Delete) FastAPI application typically consists of:
FastAPI app with route handlers (path operation functions)
SQLAlchemy models and session management as dependencies
Pydantic schemas for request/response validation
Alembic for database migrations
Dependency injection to share database sessions across routes
The pattern separates concerns cleanly: routes handle HTTP, dependencies manage resources, and models handle data persistence.
Typical Project Structure#
app/
├── main.py # FastAPI app, route registration
├── database.py # Engine, session factory, get_db dependency
├── models.py # SQLAlchemy ORM models
├── schemas.py # Pydantic request/response schemas
├── crud.py # Database query functions
└── db-migration/ # Alembic migration files
Example: CRUD Endpoints#
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from . import crud, models, schemas
from .database import engine, get_db
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
@app.post("/items/", response_model=schemas.Item)
def create_item(item: schemas.ItemCreate, db: Session = Depends(get_db)):
return crud.create_item(db=db, item=item)
@app.get("/items/", response_model=list[schemas.Item])
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
items = crud.get_items(db, skip=skip, limit=limit)
return items
@app.get("/items/{item_id}", response_model=schemas.Item)
def read_item(item_id: int, db: Session = Depends(get_db)):
db_item = crud.get_item(db, item_id=item_id)
if db_item is None:
raise HTTPException(status_code=404, detail="Item not found")
return db_item
@app.put("/items/{item_id}", response_model=schemas.Item)
def update_item(item_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)):
db_item = crud.update_item(db, item_id=item_id, item=item)
if db_item is None:
raise HTTPException(status_code=404, detail="Item not found")
return db_item
@app.delete("/items/{item_id}")
def delete_item(item_id: int, db: Session = Depends(get_db)):
success = crud.delete_item(db, item_id=item_id)
if not success:
raise HTTPException(status_code=404, detail="Item not found")
return {"detail": "deleted"}
The get_db Dependency Pattern#
from sqlalchemy.orm import Session
from .database import SessionLocal
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
FastAPI sees db=Depends(get_db), calls get_db(), injects the yielded session, and handles cleanup after the request completes.
Response Model Filtering#
Using response_model on a route automatically filters the response to only include fields defined in the schema:
class UserOut(BaseModel):
id: int
username: str
# password is NOT included — even if the ORM model has it
@app.get("/users/{user_id}", response_model=UserOut)
def get_user(user_id: int, db: Session = Depends(get_db)):
return crud.get_user(db, user_id)
This is a security feature: it prevents accidentally exposing sensitive fields even if they exist in the database model.