The “Model Duplication” Nightmare
If you’ve ever built an API with FastAPI and SQLAlchemy, you’ve likely experienced the frustration of having to define the same data structure twice.
First, you write a SQLAlchemy class to map to the database. Immediately after, you have to copy almost that exact same class over to Pydantic to validate input data and format JSON output.
# SQLAlchemy Model (for Database)
class UserDB(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
username = Column(String, index=True)
email = Column(String, unique=True)
# Pydantic Model (for API)
class UserOut(BaseModel):
id: int
username: str
email: str
With just 1 or 2 tables, it’s manageable. But as a project scales to 50-100 tables, maintaining synchronization becomes a massive burden. If you change a data type from String to Text in the DB but forget to update Pydantic, the system will throw a 500 error at your users immediately.
Why Do We Have to Suffer Like This?
The problem lies in their differing goals. SQLAlchemy focuses on converting objects into SQL statements. Meanwhile, Pydantic excels at parsing data and JSON type casting. These two libraries weren’t originally designed to understand each other.
In a real project I worked on, refactoring 30 database fields took an entire afternoon. Most of that time was spent finding and fixing the corresponding Pydantic classes to avoid schema mismatches. That’s when we need unification.
Common Workarounds
Before SQLModel arrived, developers usually chose one of two ways:
- Manual Copy-Paste: This is fast at first but acts as a “time bomb” for maintenance.
- Using conversion libraries (like pydantic-sqlalchemy): These tools work reasonably well but often struggle with new Pydantic v2 features or complex relationships.
Neither approach solves the root cause: you’re still managing two different “Sources of Truth” for the same entity.
SQLModel: One Class, Two Responsibilities
SQLModel is the brainchild of Tiangolo (the creator of FastAPI). This library inherits directly from both SQLAlchemy and Pydantic. Its goal is clear: transform a single Python class into both a database model and a data validation schema.
Quick Installation
You only need to install a single package. If you want to quickly test with SQLite, no complex drivers are required.
pip install sqlmodel
Defining a “Hybrid” Model
The magic lies in the table=True parameter. It signals to SQLModel that this class should be mapped to the database.
from typing import Optional
from sqlmodel import Field, SQLModel, create_engine, Session, select
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
secret_name: str
age: Optional[int] = None
Now, Hero plays a dual role. You use it to receive data from FastAPI’s request body and simultaneously use it to save directly to the database without any conversion back and forth.
Safe Data Manipulation
A hard-learned lesson: always use Session within a context manager (the with block). This ensures the connection is closed as soon as the work is done, preventing database hangs due to connection leaks.
def create_hero():
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
with Session(engine) as session:
session.add(hero_1)
session.commit()
session.refresh(hero_1) # Get the ID that was automatically generated by the DB
print("New Hero:", hero_1)
Layering Models in Practice
Never expose your entire database to the API. Sensitive information like hashed_password should be hidden. SQLModel supports highly flexible inheritance to handle this.
I usually organize code into 3 layers to ensure security:
- Base: Contains common fields visible to everyone.
- Create: Contains fields used only during registration (like password).
- Table: The actual class mapped to the DB, containing additional private fields.
class HeroBase(SQLModel):
name: str
age: Optional[int] = None
class HeroCreate(HeroBase):
password: str # Used only when receiving data from the client
class Hero(HeroBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
hashed_password: str # Saved to the DB, not returned to the API
Why Should You Use SQLModel Now?
If you’re starting a new FastAPI project, choose SQLModel. It reduces boilerplate code by up to 50%. More importantly, its IntelliSense support in VS Code works flawlessly. You’ll never run into typos in field names without the IDE warning you beforehand.
SQLModel doesn’t just make code shorter. It makes your code cleaner, more readable, and most importantly, easier to maintain in the long run. Try applying it to a small module, and you’ll see the difference immediately.

