SQLAlchemy Models — Designing Database Tables

In this chapter, you will learn to define data models using native SQLAlchemy ORM and manage database sessions through FastAPI dependency injection.


Why doesn't FastAPI have a built-in ORM?

Django has a built-in ORM, while Flask recommends Flask-SQLAlchemy.

FastAPI doesn't have a built-in ORM—it wants you to choose the most suitable tool,SQLAlchemy + Pydanticis the best combination recognized by the community.

(venv) $ pip install sqlalchemy

Configure database connection

Example

# File path: database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase

# SQLite database file path
SQLALCHEMY_DATABASE_URL = "sqlite:///./blog.db"

# Creating engine: check_same_thread is a SQLite-specific parameter
engine = create_engine(
    SQLALCHEMY_DATABASE_URL,
    connect_args={"check_same_thread": False}   # SQLite-specific: allow cross-threading
)

# Create session factory
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# Base class: all ORM models inherit from it
class Base(DeclarativeBase):
    pass

check_same_thread=Falseis the required configuration for SQLite in FastAPI. By default, SQLite does not allow the same connection to be used across threads, but in web applications, each request may be handled in a different thread.

Dependency injection: get_db

Example

# File path: append to database.py
from fastapi import Depends

def get_db():
    """FastAPI dependency injection: create an independent database session for each request"""
    db = SessionLocal()
    try:
        yield db          # Use this session during the request
    finally:
        db.close()        # Automatically close the session after the request ends to prevent connection leaks

Depends(get_db)is FastAPI's dependency injection system, where route functions automatically obtain database sessions through dependency injection.


Define models: Post and Category

Example

# File path: models.py
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from datetime import datetime
from database import Base

class Category(Base):
    """Article category"""
    __tablename__ = "categories"
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(50), unique=True, nullable=False)
    slug = Column(String(50), unique=True, nullable=False)
    # back_populates bidirectional relationship
    posts = relationship("Post", back_populates="category")


class Post(Base):
    """Blog Post"""
    __tablename__ = "posts"
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String(200), nullable=False)
    slug = Column(String(200), unique=True, nullable=False)
    summary = Column(Text, default="")
    content = Column(Text, nullable=False)
    category_id = Column(Integer, ForeignKey("categories.id"), nullable=False)
    created_at = Column(DateTime, default=datetime.utcnow)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    # Bidirectional relationship with Category
    category = relationship("Category", back_populates="posts")

What is used in FastAPI isNative SQLAlchemy(not Flask-SQLAlchemy'sdb.Model). Note the differences in Column imports, Base inheritance, and relationship syntax. A comparison table will be provided later.


Initialize database tables

Example

# File path: create table in startup event in main.py
from fastapi import FastAPI
from database import engine, Base
from models import Category, Post   # Ensure the model classes are imported so that Base.metadata can recognize them

app = FastAPI()

@app.on_event("startup")
def on_startup():
    """应用启动时自动创建数据库表(仅开发使用)"""
    Base.metadata.create_all(bind=engine)

Later, we will switch to Alembic migrations (Chapter 6),create_allOnly used in the rapid prototyping stage.


Insert test data

Insert test data in a Python script:

Example

# File path: seed.py (run once only)
from database import SessionLocal
from models import Category, Post

db = SessionLocal()

# Create Category
py = Category(name="Python", slug="python")
css = Category(name="CSS", slug="css")
fastapi = Category(name="FastAPI", slug="fastapi")
db.add_all([py, css, fastapi])
db.commit()

# Create Article
db.add_all([
    Post(title="The Complete Beginner's Guide to FastAPI", slug="fastapi-guide",
         summary="Learn FastAPI from Scratch", content="<h2>Why choose FastAPI?</h2><p>...</p>",
         category_id=fastapi.id),
    Post(title="Python Coroutines Explained", slug="python-coroutine",
         summary="Understand asyncio", content=<h2>What is a coroutine?</h2><p>...</p>,
         category_id=py.id),
    Post(title="CSS Grid Layout in Action", slug="css-grid",
         summary="Implement Responsive Layout with Grid", content="<h2>Introduction to Grid</h2><p>...</p>",
         category_id=css.id),
])
db.commit()
db.close()
print("Test data insertion completed!")

Runpython seed.py, insert 3 articles into the database.


Comparison of ORM syntax across four frameworks.

OperationDjangoFlask-SQLAlchemyFastAPI (native SQLAlchemy)
base classmodels.Modeldb.ModelBase (DeclarativeBase)
Fieldsmodels.CharField()db.Column(...)Column(...)
Foreign keyForeignKey(to, on_delete)db.ForeignKey('table.col')ForeignKey('table.col')
RelationshipAuto reversedb.relationship + backrefrelationship + back_populates
SessionAutomatic managementdb.sessionSessionLocal() + Depends

Chapter summary

In this chapter, you mastered SQLAlchemy integration in FastAPI: create_engine + SessionLocal to configure the database connection, DeclarativeBase to define the model base class, get_db dependency injection to manage sessions, and Column/relationship to define fields and relationships.

Compared with Flask-SQLAlchemy, native SQLAlchemy requires slightly more code but is fully open-source and controllable.

other extensions