When building applications with FastAPI and SQLModel, you have an efficient and modern stack in 2026. SQLModel combines SQLAlchemy (the ORM) and Pydantic (data validation) into a single tool.
This combination changes how we think about traditional design patterns. The following five software design patterns keep your business app maintainable and scalable.
1. The Repository Pattern - with a modern twist
In traditional enterprise apps, the Repository Pattern abstracts data access. Since SQLModel already acts as a powerful abstraction layer, a rigid repository pattern can introduce unnecessary boilerplate.
A Functional Repository or Service Layer pattern is the twist. Instead of creating massive classes for every entity, write clean reusable functions that handle database operations.
Move your complex select(), where(), and join() logic out of the path operations and into dedicated crud.py or services/ modules.
It keeps your FastAPI route handlers (endpoints) thin and focused entirely on HTTP concerns (status codes, request parsing).
# crud/user.py
from sqlmodel import Session, select
from models import User
def get_user_by_email(session: Session, email: str) -> User | None:
statement = select(User).where(User.email == email)
return session.exec(statement).first()
2. Dependency Injection Pattern
FastAPI has a fabulous and built-in Dependency Injection system (Depends).
Which is an asset for managing your database sessions.
The Depends takes a yield-based generator function as input and injects it into your endpoints.
It guarantees that database sessions are automatically opened when a request comes in and closed when the request finishes.
It also makes unit testing easier because you can override dependencies with a mock database.
# database.py
from sqlmodel import Session, create_engine
engine = create_engine("sqlite:///database.db")
def get_session():
with Session(engine) as session:
yield session
# api/routes.py
@app.get("/users/{user_id}")
def read_user(user_id: int, session: Session = Depends(get_session)):
# FastAPI automatically provides the session here
return get_user_by_id(session, user_id)
3. Data Transfer Object Pattern
Because the models from SQLModel inherit from Pydantic, they are uniquely qualified to act as data transfer objects (DTO).
You should separate your database schemas from your API request/response schemas using SQLModel’s multi-class approach. Use inheritance to create distinct flavors of your models.
You rarely want to expose everything in your database to the frontend (like password hashes or internal IDs), nor do you want to require the frontend to send an id when creating a new resource.
# models.py
from sqlmodel import SQLModel, Field
# Base properties shared across all flavors
class UserBase(SQLModel):
email: str = Field(unique=True, index=True)
username: str
# What the API expects when creating a user
class UserCreate(UserBase):
password: str
# What the API returns to the frontend (hides the password)
class UserRead(UserBase):
id: int
# The actual database table
class User(UserBase, table=True):
id: int | None = Field(default=None, primary_key=True)
hashed_password: str
4. Router/Module Pattern
As your application gorws use FastAPI’s APIRouter to implement a modular design pattern.
Organize your code so that the folder structure “screams” what the application does.
Group the code by feature rather than by technical layer, keeps the codebase decoupled. It allows for multiple developers to work on different features (e.g., users, items, billing) without merge conflicts.
app/
├── main.py
├── core/ # Configuration, security, database setup
│ ├── config.py
│ └── database.py
├── users/ # Everything related to the 'Users' domain
│ ├── router.py # Endpoints
│ ├── models.py # SQLModel classes
│ └── crud.py # DB operations
└── items/ # Everything related to the 'Items' domain
├── router.py
├── models.py
└── crud.py
5. Unit of Work Pattern
The Unit of Work pattern maintains a list of objects affected by a business transaction and coordinates the writing out of changes.
In SQLModel, the Session object is your Unit of Work.
Best Practice: Avoid calling session.commit() inside your lower-level CRUD/repository functions if a single API request needs to update multiple things. Instead, pass the session through your business logic and let the service layer or the endpoint handle the final session.commit() and session.refresh(). This ensures that if one part of the request fails, the entire transaction rolls back cleanly.
Summary
- Use FastAPI
Dependsfor database session lifecycles. - Use SQLModel inheritance (
table=Truevs standard models) to handle DTOs/Schemas. - Keep route handlers clean by moving query logic to functional CRUD modules.
- Slice your application by feature folders, not by technical layers.