KAHIBARO
Discord Login Register

12.1. What Is an ORM?

Why ORMs Exist

When you build backend applications, you almost always need to store data in a database, often a relational database such as PostgreSQL.

Relational databases understand SQL, not Python or JavaScript. As a backend developer you have two main ways to talk to the database:

  1. Raw SQL
    Write SQL strings by hand and send them to the database driver.
  2. ORM
    Use an Object Relational Mapper, which lets you work with objects in your programming language and lets the library translate those operations into SQL for you.

"ORM" stands for Object Relational Mapper. The word “mapper” is important. It maps between:

So instead of writing:

sql
INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.com');

you might write in Python:

python
user = User(id=1, name="Alice", email="alice@example.com")
session.add(user)
session.commit()

The ORM turns this object operation into the SQL command that the database understands.

Definition:
An ORM (Object Relational Mapper) is a library that maps database tables and rows to programming language classes and objects, so you can work with the database using objects and methods instead of writing raw SQL directly.


Objects vs Tables

To understand an ORM, compare how data looks in code and in a relational database.

The Database View: Tables and Rows

In a relational database, data is organized in tables. Each table has:

Example users table:

idnameemailis_active
1Alicealice@example.comtrue
2Bobbob@example.comfalse

To work with this data in SQL, you might write:

sql
SELECT id, name, email, is_active
FROM users
WHERE is_active = true;

The Code View: Classes and Objects

In code, we usually think in terms of classes and objects:

python
class User:
    def __init__(self, id, name, email, is_active=True):
        self.id = id
        self.name = name
        self.email = email
        self.is_active = is_active
# One instance (one row)
alice = User(id=1, name="Alice", email="alice@example.com", is_active=True)

In object oriented code:

How an ORM Maps Them

An ORM connects these two worlds.

You tell the ORM how your class corresponds to a table:

python
from sqlalchemy import Column, Integer, String, Boolean
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class User(Base):
    __tablename__ = "users"
    id        = Column(Integer, primary_key=True)
    name      = Column(String)
    email     = Column(String, unique=True)
    is_active = Column(Boolean, default=True)

Here the ORM learns:

From now on when you create a User instance, the ORM knows how to insert or query the matching row in the database.


How ORMs Work Conceptually

Although ORMs can look like magic, the basic ideas underneath are simple.

1. Mapping Classes to Tables

First, you define models (classes) that describe your data. The ORM records:

Internally the ORM stores some metadata like:

ConceptExample from codeDatabase side
Model / EntityUser classusers table
Field / AttributeUser.emailemail column
IdentityUser.idprimary key id
RelationshipUser.postsforeign key in posts

You only define this once, then you reuse these models across your project.

2. Translating Operations Into SQL

When you perform operations on ORM objects, the ORM creates appropriate SQL statements.

Example: creating and saving a user:

python
new_user = User(name="Alice", email="alice@example.com")
session.add(new_user)
session.commit()

Conceptually the ORM:

  1. Sees a new User object
  2. Builds an INSERT SQL statement
  3. Sends it to the database
  4. Updates the object with database generated values (for example an auto-incremented id)

Similarly, querying:

python
active_users = session.query(User).filter(User.is_active == True).all()

This becomes something like:

sql
SELECT id, name, email, is_active
FROM users
WHERE is_active = true;

The ORM reads the result rows and creates User objects for you.

Key Idea:
You work with objects and methods, but under the hood the ORM generates and executes SQL queries and then turns result rows back into objects.

3. Tracking Changes

Most ORMs have a unit of work or session concept. The session:

Example:

python
# 1. Load a user
user = session.query(User).filter(User.id == 1).first()
# 2. Modify the object
user.name = "Alice Updated"
# 3. Commit the transaction
session.commit()

You never wrote an UPDATE query. The ORM:

4. Handling Relationships

Relational databases connect tables using foreign keys. ORMs map these to object relationships.

For example you might have:

In the ORM, you might write:

python
class Post(Base):
    __tablename__ = "posts"
    id      = Column(Integer, primary_key=True)
    title   = Column(String)
    content = Column(String)
    user_id = Column(Integer, ForeignKey("users.id"))
    user = relationship("User", back_populates="posts")
class User(Base):
    __tablename__ = "users"
    id    = Column(Integer, primary_key=True)
    name  = Column(String)
    posts = relationship("Post", back_populates="user")

Now you can do:

python
user = session.query(User).filter(User.id == 1).first()
# Get all this user's posts, as a list of Post objects
user_posts = user.posts
# Create a new post for this user
new_post = Post(title="Hello", content="World", user=user)
session.add(new_post)
session.commit()

The ORM handles foreign keys and joins behind the scenes.


Benefits of Using an ORM

ORMs exist because they make most backend work easier and safer.

1. Less Boilerplate, More Productivity

Without an ORM, you repeat a lot of similar code:

python
# Raw SQL example (conceptual)
cursor.execute(
    "INSERT INTO users (name, email, is_active) VALUES (%s, %s, %s) RETURNING id;",
    ("Alice", "alice@example.com", True)
)
row = cursor.fetchone()
user_id = row[0]

With an ORM:

python
user = User(name="Alice", email="alice@example.com", is_active=True)
session.add(user)
session.commit()
# user.id is now filled in

You write less code and focus on the logic of your application, not SQL string management.

2. Safer by Default

Good ORMs use parameterized queries internally. This helps protect you from many SQL injection problems that occur when you accidentally build SQL by string concatenation.

Raw and unsafe:

python
# Very unsafe example, do NOT do this
cursor.execute(f"SELECT * FROM users WHERE email = '{user_input}'")

ORM style:

python
user = session.query(User).filter(User.email == user_input).first()

The ORM will send this to the database using parameters, not string concatenation.

3. Database Portability

If you write raw SQL tuned for a specific database, it may not work on a different database engine, or you might need to adjust many queries.

With an ORM:

This can be very helpful early in a project when you are not yet sure which database you will use in production.

4. Clearer, More Expressive Code

Queries in ORM code can be easier to read for people who are comfortable with the programming language but less experienced with SQL.

Compare:

sql
SELECT *
FROM users
WHERE is_active = true
  AND email LIKE '%@example.com'
ORDER BY id DESC
LIMIT 10;

versus an ORM style:

python
users = (
    session.query(User)
    .filter(User.is_active == True)
    .filter(User.email.ilike("%@example.com"))
    .order_by(User.id.desc())
    .limit(10)
    .all()
)

Both are fine, but in many backend codebases, having everything in one language (Python) can make it easier to refactor and reuse logic.

5. Built-in Features

Most ORMs provide many useful features out of the box:

You get a lot of common database tasks for "free."


Trade‑offs and Limitations

ORMs are powerful but not perfect. It is important to understand their limitations so you can decide when to use them and when to use raw SQL.

1. Performance Overhead

For simple queries, the performance difference between ORM and raw SQL is usually small. But in some cases:

Classic example, the N + 1 query problem:

python
# Example of a common performance issue with ORMs
users = session.query(User).all()
for user in users:
    print(user.name, len(user.posts))

If the ORM is not instructed to load posts efficiently, it might run:

So if you have 100 users, you suddenly run 101 queries.

There are ORM techniques to avoid this, such as eager loading, but you must be aware of them.

2. Complex Queries Can Become Awkward

Some very advanced SQL features or complex reporting queries can be:

In those cases, many teams will mix approaches:

3. Learning Curve

To use an ORM effectively, you need to understand:

Using an ORM does not mean you can ignore SQL completely. It is a tool that sits on top of SQL, not a replacement for understanding data and queries.

Important:
ORMs are powerful abstractions, but you still need to understand relational databases and SQL basics to use them correctly and avoid performance or correctness problems.

4. Hidden Behavior

Because ORMs do a lot automatically, it is easy to forget exactly what SQL is being executed.

Examples of hidden behavior:

If you do not know these answers, you can introduce subtle bugs or performance issues. A good practice is to log or inspect generated SQL during development.


Where ORMs Fit in a Backend Application

You will learn about application architecture and layers elsewhere in this course. Here is where ORMs typically belong.

Layers Involved

A simplified backend architecture often has:

LayerResponsibility
API / ControllerHandle HTTP requests and responses
Service LayerBusiness logic, use cases
Data AccessInteract with the database

ORMs mostly live in the data access layer.

Example Flow Without Too Much Detail

Imagine a request to POST /users to create a new user.

High level sequence:

  1. FastAPI endpoint receives the HTTP request.
  2. The endpoint calls a service function like create_user(...).
  3. The service constructs a User ORM object.
  4. The ORM session adds and commits the new object.
  5. The service returns a data object or model to the API layer.
  6. The API layer returns a JSON response.

Only step 3 and 4 care about the ORM. The rest of the application does not need to know that the database is a relational database or that SQL is being used.

Often developers combine ORMs with patterns like the Repository Pattern to keep ORM details isolated. That pattern is covered in a later chapter.


Examples of Common ORM Operations

Here are a few concrete examples to show how an ORM typically feels, without going deep into any specific ORM library.

Creating a Record

SQL way:

sql
INSERT INTO users (name, email, is_active)
VALUES ('Alice', 'alice@example.com', true)
RETURNING id;

ORM style:

python
user = User(name="Alice", email="alice@example.com", is_active=True)
session.add(user)
session.commit()
# Now user.id is available
print(user.id)

Reading Records

SQL way:

sql
SELECT id, name, email, is_active
FROM users
WHERE is_active = true
ORDER BY id
LIMIT 5;

ORM style:

python
active_users = (
    session.query(User)
    .filter(User.is_active == True)
    .order_by(User.id)
    .limit(5)
    .all()
)
for user in active_users:
    print(user.id, user.name)

Updating Records

SQL way:

sql
UPDATE users
SET is_active = false
WHERE id = 1;

ORM style:

python
user = session.query(User).filter(User.id == 1).first()
user.is_active = False
session.commit()

Deleting Records

SQL way:

sql
DELETE FROM users
WHERE id = 1;

ORM style:

python
user = session.query(User).filter(User.id == 1).first()
session.delete(user)
session.commit()

These examples show the typical pattern:

The ORM manages the SQL details.


When to Use an ORM and When Not To

Although many modern backend applications use an ORM, it is not mandatory.

Good Situations for an ORM

An ORM is usually a good choice when:

For the projects in this course, you will see how an ORM supports building real backend applications faster and more reliably.

Situations Where Raw SQL Might Be Better

Sometimes you might prefer raw SQL or a more lightweight approach:

Many teams use both:

You do not have to choose one forever. You can mix them in the same project where it makes sense.


Summary

In the following chapters you will see how to use a specific ORM, how to define models, work with database sessions, and perform all the common database operations in real backend applications.

Views: 6

Comments

Please login to add a comment.

Don't have an account? Register now!