SQLAlchemy is a Python SQL toolkit and ORM that simplifies working with relational databases such as SQLite, PostgreSQL, and MySQL. Learn how to create database models, manage sessions, define relationships, and perform CRUD operations using practical Python examples. Explore SQLAlchemy Core vs ORM, best practices, and integration with Flask and FastAPI for building efficient, maintainable backend applications.
SQLAlchemy is one of the most widely used Python libraries for interacting with relational databases. It helps developers manage database connections, execute SQL queries, define relationships between tables, and perform CRUD operations using Python.
Instead of manually writing SQL queries for every database operation, developers can use SQLAlchemy’s Object Relational Mapper (ORM) to work with database records as Python objects. It also provides SQLAlchemy Core for developers who need more direct control over SQL expressions and database operations.
In this guide, you will learn how SQLAlchemy works, how to connect Python to a database, define ORM models, manage sessions, perform CRUD operations, and integrate SQLAlchemy with Flask and FastAPI.
SQLAlchemy is an open-source SQL toolkit and Object Relational Mapper (ORM) for Python. It connects Python applications with relational databases such as SQLite, PostgreSQL, MySQL, and Oracle.
An ORM maps database tables to Python classes and database rows to Python objects. This allows developers to create, retrieve, update, and delete database records through Python code.
For example, a database table named users can be represented by a Python class called User. Each instance of that class can represent a user record.
SQLAlchemy supports two main approaches:
These approaches can be used independently or together, depending on the application’s requirements.
Understanding the difference between SQLAlchemy Core and ORM helps developers choose the right approach for a project.
| Feature | SQLAlchemy Core | SQLAlchemy ORM |
| Main purpose | SQL expression and database access | Object-relational mapping |
| Data representation | Tables and rows | Python classes and objects |
| Query approach | SQL expressions | ORM queries and SQL expressions |
| Relationship handling | Explicit joins and table operations | Object relationships |
| Best suited for | Fine-grained SQL control | Application data models |
| Transaction support | Yes | Yes, through the session and underlying connection |
For example, SQLAlchemy Core can select data directly from a table, while the ORM can retrieve records as Python objects.
Both approaches use SQLAlchemy’s underlying database connectivity and SQL expression capabilities.
Install SQLAlchemy using pip:
pip install sqlalchemyFor this example, SQLite is used because it does not require a separate database server.
Create a database engine:
from sqlalchemy import create_engine
engine = create_engine(
"sqlite:///blog.db",
echo=True
)The create_engine() function configures the database connection interface and manages connections through a connection pool where appropriate.
The sqlite:///blog.db URL specifies a SQLite database file named blog.db in the current working directory.
The echo=True parameter prints SQL statements and related logging information, making it useful for learning and debugging.
A SQLAlchemy ORM model is a Python class mapped to a database table. In SQLAlchemy 2.0, the declarative mapping style uses DeclarativeBase, Mapped, and mapped_column().
The following example defines a User model and a Post model with a one-to-many relationship.
from sqlalchemy import String, ForeignKey
from sqlalchemy.orm import (
DeclarativeBase,
Mapped,
mapped_column,
relationship
)
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(50))
email: Mapped[str] = mapped_column(
String(100),
unique=True
)
posts: Mapped[list["Post"]] = relationship(
back_populates="author"
)
class Post(Base):
__tablename__ = "posts"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String(120))
user_id: Mapped[int] = mapped_column(
ForeignKey("users.id")
)
author: Mapped["User"] = relationship(
back_populates="posts"
)
Base.metadata.create_all(engine)Here is what the main components do:
__tablename__ specifies the database table name.mapped_column() defines mapped columns and their properties.primary_key=True identifies the primary key column.ForeignKey("users.id") links a post to a user.relationship() defines the object-level relationship between the models.Base.metadata.create_all(engine) creates the mapped tables that do not already exist.A user can have multiple posts, while each post references one user. The back_populates argument connects both sides of the relationship.
create_all() creates missing tables but does not automatically migrate existing tables when the model structure changes. For ongoing schema changes, use a migration tool such as Alembic.A SQLAlchemy session manages ORM objects and coordinates database operations within a unit of work. It tracks changes to objects and flushes those changes to the database when required.
The following example creates a user and a related post, commits the transaction, and retrieves the user.
from sqlalchemy import select
from sqlalchemy.orm import Session
with Session(engine) as session:
user = User(
name="Naveen",
email="naveen@example.com"
)
user.posts.append(
Post(title="Learning SQLAlchemy")
)
session.add(user)
session.commit()
stmt = select(User).where(
User.name == "Naveen"
)
found = session.scalars(stmt).first()
if found:
print(found.email)
print(len(found.posts))The Session context manager closes the session when the block ends. The commit() method commits the transaction, while select() constructs a query using SQLAlchemy’s expression API.
The session also tracks related objects, so the user and post can be persisted together through the same transaction.
In production applications, use short-lived sessions and handle database errors appropriately. If a flush or commit fails, call session.rollback() before reusing that session.
CRUD stands for Create, Read, Update, and Delete. These four operations form the foundation of most database-driven applications.
Add a new object to the session and commit it.
with Session(engine) as session:
user = User(
name="Priya",
email="priya@example.com"
)
session.add(user)
session.commit()Use select() to retrieve data from the database.
with Session(engine) as session:
stmt = select(User).where(
User.name == "Priya"
)
user = session.scalars(stmt).first()
if user:
print(user.email)Load the record, modify its attributes, and commit the changes.
with Session(engine) as session:
user = session.scalars(
select(User).where(User.name == "Priya")
).first()
if user:
user.name = "Priya Sharma"
session.commit()SQLAlchemy detects the modified attribute and generates the appropriate UPDATE statement when changes are flushed.
Use session.delete() to mark a persistent object for deletion.
with Session(engine) as session:
user = session.scalars(
select(User).where(
User.name == "Priya Sharma"
)
).first()
if user:
session.delete(user)
session.commit()This example deletes the user record. If related posts exist, the database’s foreign-key rules and the ORM relationship configuration determine how deletion is handled. Configure deletion behavior deliberately rather than assuming related records will always be removed automatically.
| Operation | SQLAlchemy ORM approach | Purpose |
| Create | session.add(obj) and session.commit() | Insert a record |
| Read | session.scalars(select(Model)).all() | Retrieve records |
| Update | Modify object attributes and commit | Change existing data |
| Delete | session.delete(obj) and commit | Remove a record |
Database relationships connect related information stored in separate tables. SQLAlchemy ORM supports common relationship patterns, including one-to-many, many-to-one, one-to-one, and many-to-many.
In the example above, a user can publish multiple posts. The user_id foreign key stores the relationship in the database, while User.posts and Post.author provide convenient object-level navigation.
Relationships are useful in blogging platforms, e-commerce applications, learning management systems, and customer management systems.
For example, an e-commerce database might connect customers to orders and orders to order items. Properly defined foreign keys and ORM relationships help maintain data integrity and make related records easier to query.
SQLAlchemy is commonly used in Python backend development with frameworks such as Flask and FastAPI.
Both frameworks benefit from separating database models, request validation, business logic, and API routes. Pydantic is commonly used with FastAPI to validate request data and serialize response data.
For larger applications, this separation makes testing, debugging, and maintaining database operations easier.
Following good database practices helps make SQLAlchemy applications more reliable, secure, and maintainable.
create_all() to modify existing tables.selectinload() when retrieving collections of related objects.These practices are particularly important in production applications where multiple users, concurrent requests, and complex database relationships are involved.
One common mistake is forgetting to commit a transaction, which can leave intended changes unsaved. Another is sharing a single session between concurrent requests or tasks, which can cause incorrect session usage and difficult-to-debug behavior.
Beginners may also assume that Base.metadata.create_all() updates existing database tables. It does not provide a complete migration mechanism, so schema changes should be handled with migrations.
Finally, avoid constructing SQL statements by joining user input into strings. SQLAlchemy’s expression system supports parameter binding, helping protect queries from SQL injection when used correctly.
Understanding these details early helps developers build safer and more predictable Python database applications.
SQLAlchemy in Python provides a flexible way to interact with relational databases through both SQL expressions and object-relational mapping. By learning engines, models, sessions, relationships, and CRUD operations, beginners can develop a strong foundation for backend development.
Start with SQLite, inspect generated SQL using echo=True, and practise creating, reading, updating, and deleting records. As your projects grow, explore Alembic migrations, transaction management, query optimization, and integration with Flask or FastAPI.
With these concepts in place, SQLAlchemy becomes a practical tool for building maintainable, database-driven Python applications.
SQLAlchemy connects Python applications to relational databases. It supports SQL query construction, database connections, transactions, ORM models, and CRUD operations.
It is both. SQLAlchemy Core provides SQL expression and database connectivity features, while SQLAlchemy ORM maps Python classes to database tables.
It depends on the use case. SQLAlchemy reduces repetitive database code and provides ORM features, while raw SQL can be useful for specialized queries. Developers can use both approaches in the same application.
Yes. SQLAlchemy supports multiple database backends, although database-specific features, connection drivers, data types, and SQL behavior can differ.
A session manages ORM objects, tracks their changes, and coordinates database transactions. Developers use it to add, retrieve, update, and delete records.
SQLAlchemy handles database connectivity, SQL expressions, and ORM operations. Alembic is a database migration tool commonly used with SQLAlchemy to manage schema changes over time.
Indian Institute of Embedded Systems – IIES