@seclink: Sharing a new article 'Is it necessary to migrate an application based on FastAPI + SQLAlchemy ORM to FastAPI + SQLModel?' https://juejin.cn/post/7648902852933304360…
Summary
This article compares the usage scenarios of FastAPI+SQLAlchemy and SQLModel, analyzes the benefits and costs of migration, and suggests that existing projects do not need to fully migrate.
View Cached Full Text
Cached at: 06/09/26, 02:54 PM
Sharing a new article: “For an application based on FastAPI + SQLAlchemy ORM, is it necessary to switch to FastAPI + SQLModel?” https://juejin.cn/post/7648902852933304360…
For an application based on FastAPI + SQLAlchemy ORM, is it necessary to switch to FastAPI + SQLModel?
Source: https://juejin.cn/post/7648902852933304360 In Python web development (especially the FastAPI + SQLAlchemy ecosystem), Models and Schemas are two frequently mentioned concepts with completely different responsibilities.
In simple terms: Models are responsible for “how to store data in the database,” while Schemas are responsible for “how to validate and filter data for API input and output.”
I. Core Concept Comparison
| Dimension | Models (Database Model / Entity) | Schemas (Validation Model / DTO) |
|---|---|---|
| Main Purpose | Data Persistence | Data Transfer & Boundary Validation (Validation & DTO) |
| Representative Framework | SQLAlchemy, Tortoise-ORM, Django ORM | Pydantic, Marshmallow |
| Location | Maps to database Table Schema | API’s Input Parameters (Request) and Output Data (Response) |
| Core Responsibilities | - Map table names, primary keys, foreign keys, indices - Handle database relationships - Execute SQL reads/writes | - Validate field types, lengths, formats (e.g., Email, phone number) - Filter sensitive data (e.g., hide password fields) - Serialize to JSON / deserialize |
II. A Vivid Analogy for Understanding
Imagine you are running an exclusive private club (system):
- Models are the “employee files” inside the club:- They contain employees’ ID numbers, real names, home addresses, contract numbers, bank card numbers (equivalent to all sensitive and physical fields in the database).
- Only the manager (database/ORM) can see this file; it must never be shown to guests. - Schemas are the “employee name badges”:- The badge only shows: stage name (Nick Name), job position (exposes only the information guests need to see).
- Additionally, when a guest wants to join, they fill out a “membership application form” (input Schema), and the format needs to be checked before the badge is made (data validation).
III. Data Flow Diagram (Their Position in the System)
In a complete API request, each stands guard at a different checkpoint:
📥 [Client Request (JSON)]
│
▼
┌──────────────────────┐
│ Schemas (Pydantic) │ <--- 1. Intercept and validate data (e.g., Is the password long enough? Is the Email format correct?)
└──────────────────────┘
│ (Validation passes, convert to Python object)
▼
┌──────────────────────┐
│ Service / Controller │ <--- 2. Business logic processing
└──────────────────────┘
│ (Convert to DB Model object)
▼
┌──────────────────────┐
│ Models (SQLAlchemy) │ <--- 3. Map to a database table row, write to DB
└──────────────────────┘
│
▼
💾 [Database (DB)]
IV. Code Example Comparison
1. Models Implementation (using SQLAlchemy)
It focuses on underlying database details.
# app/models/user.py
from sqlalchemy import Column, Integer, String, Boolean
from app.database import Base
class DBUser(Base):
__tablename__ = "users" # Physical table name
id = Column(Integer, primary_key=True, index=True) # Database primary key
email = Column(String, unique=True, index=True, nullable=False)
hashed_password = Column(String, nullable=False) # Database stores hashed ciphertext
is_active = Column(Boolean, default=True)
2. Schemas Implementation (using Pydantic)
It focuses on API input and output.
# app/schemas/user.py
from pydantic import BaseModel, EmailStr, Field
# Data sent by client during registration (Input Schema)
class UserCreate(BaseModel):
email: EmailStr # Automatically validates that the format is a valid email
password: str = Field(min_length=8) # Forces password to be at least 8 characters
# Data returned to the client (Output Schema / DTO)
class UserResponse(BaseModel):
id: int
email: EmailStr
# 🚫 Note: Never write password or hashed_password here
# This ensures sensitive password information never leaks to the frontend through the API
V. Why Can SQLModel Combine the Two?
Because in traditional development, DBUser and UserResponse have a lot of field duplication (e.g., id, email). Modifying one field requires changing two files.
SQLModel cleverly uses Python’s multiple inheritance to let one class play both roles simultaneously:
- When it includes
table=True, it acts as a Model (generates database table). - When it does not include
table=True, it acts as a Schema (performs API validation and filtering).
For an existing project that is stably running on FastAPI + SQLAlchemy ORM, there is usually no absolute necessity to perform a full refactoring to upgrade to SQLModel.
Although SQLModel brings an excellent development experience, the refactoring cost and potential risks of a full migration often outweigh the direct benefits it provides.
Here is an in-depth comparative analysis of migration benefits, costs/risks, and decision recommendations:
I. Core Benefits of Migrating to SQLModel (Why do it?)
If you choose to migrate, you will gain the following three most significant “sweet spots”:
1. Eliminate Dual Definitions, Halve the Code (DRY - Don’t Repeat Yourself)
- Current State (SQLAlchemy): Adding a new table field requires modifying
models.py(SQLAlchemy),schemas.py(UserCreate), andschemas.py(UserResponse). It’s easy to miss an update. - SQLModel: You only need to add one line in the
BaseModel. Database migration and API input/output validation will automatically synchronize and take effect.
2. Perfect IDE Autocomplete and Type Safety (Developer Experience)
- Current State (SQLAlchemy): Executing
db.execute(select(User))returns aResultobject. You must convert it via.scalars().all(), and the IDE (VSCode/PyCharm) often cannot infer that the returned list containsUserentities, resulting in no property hints when writing subsequent code. - SQLModel:
db.exec(select(User)).all()will be accurately recognized by the IDE aslist[User]. From query to field access, the autocomplete experience is excellent throughout.
3. Native Seamless Integration with FastAPI
- SQLModel is developed by FastAPI’s author, Tiangolo, himself. SQLModel instances can be directly returned as FastAPI’s
response_modelor directly received as Request Body, saving many conversion steps likeBaseModel.model_validate(db_obj).
II. Costs and Potential Risks of Migration (The Cost & Risk)
1. Compatibility Risks with Advanced ORM Features
Although SQLModel is built on top of SQLAlchemy, it wraps some complex advanced features of SQLAlchemy. If your project heavily uses the following features, you will encounter significant resistance during migration:
- Multi-table inheritance (e.g., Joined Table Inheritance, Polymorphic Queries).
- Complex composite primary/foreign key relationships.
- Highly customized
relationshipattributes (e.g., custom association proxies or non-standard join conditions). - In these scenarios, SQLModel may have type system conflicts or force you to regress to writing
sa_column = Column(...), thus losing the simplicity that SQLModel originally offers.
2. Workload for Existing Code and Testing Regression
- Modify table definitions: All models inheriting from
declarative_base()must be refactored to inherit fromSQLModel(table=True). AllColumnfields must be rewritten asField. - Modify query statements: Although old SQLAlchemy queries (
db.execute(...)) are still compatible in SQLModel, you will likely need to gradually rewrite them todb.exec(...)for type safety. - Alembic migration scripts: Generated database migration scripts (Migrations) need careful handling after changing the model base class to prevent Alembic from mistakenly detecting tables as being dropped and recreated.
III. Decision Matrix: How to Choose?
| Project Status | Recommendation | Rationale & Implementation Strategy |
|---|---|---|
| New Project / Microservice | 🚀 Decisively use SQLModel | No historical baggage; you can fully enjoy the rapid development experience SQLModel offers. |
| Small to Medium-sized Existing Project (Model count < 15, simple table structure) | ⚖️ Migration is worth considering | Refactoring time is controllable within 1-2 days; can completely eliminate redundant code and improve subsequent maintenance efficiency. |
| Large Enterprise-level Project (Complex table structure, heavily reliant on SQL optimization) | 🛑 Maintain SQLAlchemy status quo | Safety first. SQLAlchemy’s ecosystem is more mature with extensive industrial troubleshooting experience. The risk of regression bugs from refactoring far outweighs the syntactic sugar benefits of SQLModel. |
| Want Partial Optimization | 🛠️ Mix and match (No conflict) | Recommended. Because SQLModel is built on SQLAlchemy, its Session is compatible with regular SQLAlchemy Models. You can use SQLModel for newly developed modules in existing projects while keeping existing modules unchanged, transitioning gradually. |
💡 Final Recommendation
- If the current project runs stably and has no frequent model change requirements, do not modify it.
- If you are suffering from “API DTO and database Model fields being out of sync” and “IDE has no SQL hints”, you can first pick a non-core sub-business module for a partial refactoring with SQLModel, verify the results, and then decide whether to roll it out fully.
Comparing the low-level native driver (psycopg2) with high-level ORM (SQLAlchemy / SQLModel) is essentially a trade-off between “extreme performance and fine-grained control” and “development efficiency and engineering maintainability”.
Here is an in-depth comparison of the pros and cons of the two:
Compared to using psycopg2 directly, what are the pros and cons of using FastAPI + SQLAlchemy ORM or FastAPI + SQLModel?
I. Direct Use of psycopg2 (Native Driver)
psycopg2 is a low-level C language driver for PostgreSQL that executes raw SQL directly.
Advantages:
- Extreme Performance (Zero Overhead): No ORM framework in the middle does “SQL translation” and “object instantiation (Row -> Python Object)” overhead. Performance nearly reaches the physical limit of Python operating PostgreSQL.
- Full Control Over SQL: You can write any complex PostgreSQL-specific syntax (e.g., window functions, recursive CTEs, JSONB operations, full-text search) without having to research the corresponding weird syntax of an ORM.
- Lighter Weight: Fewer project dependencies, faster startup, and no complex ORM state management (e.g., pitfalls from Session states, lazy loading, etc.).
Disadvantages:
- Extremely Low Development Efficiency: Even simple CRUD operations require writing complete SQL statements manually, and you must handle placeholders (e.g.,
%s) manually to prevent SQL injection. - Lack of Type Safety and Autocomplete: SQL is written as string literals in the code. An IDE (e.g., VSCode/PyCharm) cannot perform syntax checking, autocomplete, or refactoring on fields within SQL strings.
- Manual Result Set Mapping:
psycopg2by default returns a list of tuples (list[tuple]) or dictionaries (dict). You must write your own code to parse and map them into Python objects or JSON, which is error-prone. - Synchronous and Blocking (Conflicts with FastAPI):
psycopg2is a synchronous blocking driver. In FastAPI’s asynchronous event loop, directly executing slow queries will block the entire service. Although this can be mitigated with thread pools or switching topsycopg3/asyncpg, it increases development complexity.
II. Using FastAPI + SQLAlchemy / SQLModel (High-Level ORM)
Advantages:
- Extremely High Development Efficiency: Provides an object-oriented operation approach. For example, creating a record is just
db.add(user); querying is justselect(User), without writing tedious INSERT/UPDATE statements. - Automatic Data Validation and Serialization (with Pydantic):- Input: FastAPI automatically validates and converts frontend JSON into Pydantic/SQLModel objects.
- Output: Returns ORM objects directly; FastAPI automatically filters sensitive fields (e.g., passwords) and serializes them to JSON. - Seamless Async/Await Support: SQLAlchemy (v1.4+) and SQLModel perfectly support async context (with the
asyncpgdriver), fitting flawlessly with FastAPI’s asynchronous architecture without blocking the event loop. - Database Migration Tool (Alembic): You can automatically generate database schema migration scripts (Schema Migrations) from Python code (Models), making version control extremely simple.
- Defense Against SQL Injection: The ORM automatically uses parameterized queries under the hood, eliminating most SQL injection risks at the source.
Disadvantages:
- Performance Overhead: Due to multiple layers of wrapping (SQL generation, result set object mapping, Pydantic validation, etc.), CPU and memory usage in high-concurrency, high-throughput scenarios is significantly higher than
psycopg2. - N+1 Query Pitfall: If you are not familiar with ORM’s “Lazy Loading” mechanism, it’s easy to write code that queries the database in a loop, generating many unnecessary database connection requests and drastically slowing down the system.
- Steep Learning Curve: SQLAlchemy is an extremely large and complex library. The learning cost and troubleshooting effort when dealing with complex joins, transaction isolation levels, and connection pool tuning can be high.
III. Core Dimension Comparison Table
| Dimension | psycopg2 (Raw SQL) | FastAPI + SQLAlchemy / SQLModel |
|---|---|---|
| Query Performance | 🥇 Extremely Fast (no intermediate overhead) | 🥈 Slower (affected by ORM mapping and validation overhead) |
| Development Speed | ❌ Slow (write a lot of boilerplate SQL and data conversion code) | 🥇 Extremely Fast (automatic CRUD, automatic DTO conversion) |
| Async Support | ❌ Not natively supported (requires thread pool or switching to asyncpg) | 🥇 Natively supported (perfect async with asyncpg) |
| Type Hints / Autocomplete | ❌ None (SQL is just strings) | 🥇 Perfect (IDE can accurately infer model attributes) |
| Maintenance Cost | ❌ High (changing a field name requires searching all SQL strings globally) | 🥇 Low (modify Model only, supports IDE refactoring) |
| SQL Injection Prevention | ⚠️ Requires manual parameterization (easy to write vulnerable code inadvertently) | 🥇 Naturally prevented (framework enforces parameterization under the hood) |
IV. Engineering Practice Recommendations: How to Choose?
In real-world enterprise FastAPI development, we rarely go to either extreme. We usually adopt a Hybrid Approach:
- 80% of business (standard CRUD / business logic): Use FastAPI + SQLModel / SQLAlchemy.
- Benefit from automatic validation, rapid development, type completion, and Alembic automatic migrations. - 20% of business (high performance / reports / complex joins): Execute raw SQL within the ORM Session.
- SQLAlchemy allows you to execute raw SQL directly viasession.execute(text("SELECT ...")). This retains the ORM’s connection pool management while gaining the extreme performance and flexibility of raw SQL.
- Note: If using only raw SQL, it’s recommended to useasyncpginstead of synchronouspsycopg2in FastAPI.
Similar Articles
@FinanceYF5: Jason: A brand-new real-time translation model, now available for API trial.
Jason has released a brand-new real-time translation model, which can currently be trialed via the API.
@Michaelzsguo: https://x.com/Michaelzsguo/status/2053217839729791221
This article is a guide for local large model deployment, covering hardware selection, memory calculations, Runtime tool comparisons, and model quantization options, helping users from getting started to optimizing their local inference experience.
@QingQ77: Decouple Alibaba DAMO Academy's ZipEnhancer noise reduction model from the ModelScope pipeline and package it as a high-performance FastAPI denoising service. https://github.com/gyj1201/zipEnhancer… Alibaba DAMO Academy's Z…
This project decouples Alibaba DAMO Academy's ZipEnhancer noise reduction model from the ModelScope pipeline, rewrites the inference logic in pure PyTorch, and packages it as a FastAPI service. It supports FP16 half-precision and long audio segmentation, providing multiple noise reduction model switching and API interfaces.
@seclink: Share "Developer Ecosystem Information Gap Report (2026-06-05)", interested friends can read. - Now some people are beginning to migrate openclaw-type projects to AI glasses, AI rings, and other wearable devices ... - Robotics and embodied AI open source: many open source datasets and open source models ...
Shared an information gap report on the developer ecosystem, covering topics such as migrating openclaw-type projects to wearable devices like AI glasses and rings, open-source data and models for robotics and embodied AI, and niche open-source applications for AI API relay stations and routing.
@beefnoode: https://x.com/beefnoode/status/2062816409030389909
Based on a16z's analysis of Salesforce's headless products, this article explores the trend of enterprise software moats shifting from user interfaces to underlying data models, permission systems, and workflow logic in the AI Agent era, and analyzes the difficulty differences in migrating CRM, ATS, ERP, and other systems.