SQLModel: Python ORM for FastAPI and SQLAlchemy
Learn SQLModel, the modern Python ORM that combines SQLAlchemy and Pydantic for FastAPI.
20+ years shipping production Python across data and backend systems. Lessons pulled from things that broke in production.
- ✓Python 3.7+ installed
- ✓Basic knowledge of SQL and databases
- ✓Familiarity with FastAPI or Flask
- ✓Understanding of Python type hints
SQLModel is a Python ORM that merges SQLAlchemy's database power with Pydantic's data validation, designed for FastAPI. It lets you define models once, get automatic API schemas, and use async/await. Key benefits: less boilerplate, type safety, and seamless FastAPI integration.
Think of SQLModel as a smart translator between your Python code and a database. Normally, you'd write two sets of instructions: one for the database (SQLAlchemy) and one for data validation (Pydantic). SQLModel combines them into one, so you only write once. It's like having a bilingual friend who can talk to both the database and your API without repeating yourself.
When building APIs with FastAPI, you need to interact with a database. Traditionally, you'd use SQLAlchemy for ORM and Pydantic for data validation, leading to duplicated model definitions. SQLModel, created by FastAPI's author, solves this by merging both into a single model. This tutorial will guide you through SQLModel's core features, from basic CRUD to advanced relationships, with production-ready examples. You'll learn how to define models, perform queries, handle migrations, and debug issues in production. By the end, you'll be able to build robust FastAPI applications with minimal boilerplate.
Setting Up SQLModel
First, install SQLModel: pip install sqlmodel. It pulls in SQLAlchemy and Pydantic automatically. Create a database engine and session. Here's a minimal setup:
pool_size=10 and max_overflow=20 to handle concurrent requests.create_engine.Defining Models
Models are defined by inheriting from SQLModel. Each attribute is a column. Use type hints for validation. Example: a Hero model with id, name, secret_name, and age.
id as Optional[int] with default None so SQLAlchemy auto-generates it. For UUIDs, use Field(default_factory=uuid.uuid4).Field for column constraints like primary_key, nullable, etc.CRUD Operations
CRUD (Create, Read, Update, Delete) is straightforward with sessions. Here's how to create and read heroes:
session.rollback() to reset the session.session.exec() for queries (returns a Result object) and session.add() for inserts. Always commit and refresh to get generated IDs.Relationships: One-to-Many
SQLModel supports relationships via Relationship and foreign keys. Example: Team has many Heroes.
selectinload to eagerly load relationships and avoid N+1 queries: statement = select(Team).options(selectinload(Team.heroes)).Field(foreign_key="table.column") and use Relationship to navigate between models.Using SQLModel with FastAPI
SQLModel shines in FastAPI. Define request/response models as non-table models, and database models as table=True. Use dependency injection for sessions.
response_model to endpoints to automatically filter fields. For example, response_model=HeroRead where HeroRead excludes secret fields.from_orm or model_validate to convert.Migrations with Alembic
SQLModel works with Alembic for database migrations. Install alembic and configure it to use your SQLModel metadata.
target_metadata = SQLModel.metadata in Alembic. Run alembic revision --autogenerate -m "message" to generate migrations.Async Support
SQLModel supports async with databases like PostgreSQL. Use AsyncSession and create_async_engine.
asyncio.Lock if sharing sessions across tasks.create_async_engine and AsyncSession for async operations. All session methods become awaitable.The Missing Foreign Key: A Production Outage
- Always set new foreign keys as nullable initially in production.
- Use database migrations to apply schema changes safely.
- Test schema changes against a copy of production data.
- Add database constraint checks in CI/CD pipelines.
- Monitor error rates after deployments to catch regressions early.
session.rollback() and log the failing data.SELECT COUNT(*) FROM table WHERE new_column IS NULL;ALTER TABLE table ALTER COLUMN new_column DROP NOT NULL;| File | Command / Code | Purpose |
|---|---|---|
| setup.py | from sqlmodel import SQLModel, create_engine, Session | Setting Up SQLModel |
| models.py | from typing import Optional | Defining Models |
| crud.py | from sqlmodel import Session, select | CRUD Operations |
| relationships.py | from typing import Optional, List | Relationships |
| fastapi_app.py | from fastapi import FastAPI, Depends | Using SQLModel with FastAPI |
| alembic_env.py | from sqlmodel import SQLModel | Migrations with Alembic |
| async_example.py | from sqlmodel import SQLModel, create_async_engine, select | Async Support |
Key takeaways
table=True for database models and non-table models for API schemas.Interview Questions on This Topic
How does SQLModel combine SQLAlchemy and Pydantic?
Frequently Asked Questions
20+ years shipping production Python across data and backend systems. Lessons pulled from things that broke in production.
That's Python Libraries. Mark it forged?
3 min read · try the examples if you haven't