Understanding the Relationship Between FastAPI, SQLAlchemy, ORM, and Database Connections

FastAPI is taking the Python web development world by storm specially for developer who wants to develop AI based application. To master it, we need a solid understanding of the core components that make it so fast and popular. This article dives into the essential, fundamental conceptsβ€”the must-learn connectorsβ€”every developer needs to know to build robust applications with FastAPI. This is mine journey of learning fastapi. This is our first tutorial notes on FASTAPI.

🧠 1. What Is SQLAlchemy?

SQLAlchemy is a powerful Python library that provides a consistent way to interact with relational databases such as:

  • PostgreSQL
  • MySQL
  • SQLite
  • SQL Server

It acts as an abstraction layer or Interface kind of thing β€” meaning, your code doesn’t care which database engine is used. You just change the database URL, and SQLAlchemy takes care of the rest. Example:

from sqlalchemy import create_engine

# Connect to PostgreSQL
engine = create_engine("postgresql://user:password@localhost/mydb")

# Change to SQLite without touching other code
# engine = create_engine("sqlite:///./test.db")

βœ… You use the same API for any relational database. That’s why SQLAlchemy is called an abstraction layer β€” it hides DB-specific complexity behind a common Python interface.

🧩 2. SQLAlchemy ORM β€” The Object-Relational Mapper

ORM stands for Object-Relational Mapping. It allows you to treat database tables like Python classes and rows like Python objects. Example (models.py):

from sqlalchemy import Column, Integer, String, Float
from database import Base

class Product(Base):
    __tablename__ = "products"
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String)
    description = Column(String)
    price = Column(Float)

Instead of writing SQL queries like:

SELECT * FROM products WHERE id = 1;

You can write in Python:

product = db.query(Product).filter(Product.id == 1).first()

βœ… The ORM automatically translates this Python query into SQL.
βœ… This improves readability, security, and portability.

Where the from database import Base came from ? So, it’s imported from your local database.py file, not directly from SQLAlchemy. But inside that database.py, you’ll find something like this:

from sqlalchemy.ext.declarative import declarative_base.

Base = declarative_base()

βš™οΈ What Does declarative_base() Do?
The declarative_base() is a SQLAlchemy function that creates a base class for all your ORM models. Think of it like this: It’s the parent class that tells SQLAlchemy β€” β€œHey, any class that inherits from me is a table in the database.” So when you write:

class Product(Base):
    __tablename__ = "products"
    id = Column(Integer, primary_key=True, index=True)

βœ… SQLAlchemy automatically understands:

  • β€œThis is a table named products.”
  • β€œThese are its columns and types.”
ComponentDescription
declarative_base()SQLAlchemy function that returns a Base class
BaseYour app’s root class for all ORM models
Product(Base)Inheriting from Base registers this model as a database table
Fastapi tutorial

βš™οΈ 3. What Is create_engine()?

create_engine() is the core entry point to the database. It creates a connection engine, which:

  • Knows how to talk to your specific database (PostgreSQL, SQLite, etc.)
  • Manages the connection pool
  • Is created once when your app starts
from sqlalchemy import create_engine

engine = create_engine(
    "postgresql://user:password@localhost/mydb",
    pool_size=10,
    max_overflow=20,
)

πŸŠβ€β™‚οΈ 4. Understanding Connection Pools

A connection pool is a collection of ready-to-use database connections managed by SQLAlchemy.

❌ Without Pooling

Every API request would:

  1. Open a new database connection
  2. Authenticate
  3. Run a query
  4. Close the connection

That’s very slow (opening a DB connection can take hundreds of milliseconds).

βœ… With Pooling

When your app starts:

  • SQLAlchemy opens a few database connections (e.g., 5–10)
  • Keeps them alive in memory
    Each API request:
  • Borrows a connection from the pool
  • Runs its query
  • Returns the connection to the pool when done

So your app reuses connections instead of creating new ones every time β€” this makes it faster and more scalable.

πŸ”„ 5. How FastAPI Manages Sessions per Request

In a FastAPI app, each API request gets its own SQLAlchemy session, created from a global SessionLocal.

from sqlalchemy.orm import sessionmaker

SessionLocal = sessionmaker(bind=engine)

In main.py, you define a dependency:

from fastapi import Depends

def get_db():
    db = SessionLocal()  # get a session (borrows connection)
    try:
        yield db
    finally:
        db.close()  # return connection to pool

Each route uses it:

@app.get("/products")
def get_products(db: Session = Depends(get_db)):
    return db.query(Product).all()

βœ… Each API request β†’ gets its own independent session
βœ… Each session β†’ borrows a connection from the shared pool
βœ… When done β†’ connection is returned to pool for reuse

🧱 6. Why Do We Need Connection Pools?

Without PoolWith Pool
Each API call opens a new DB connectionReuses existing connections
Slow performanceFast, low latency
High DB loadEfficient use of resources
Risk of hitting DB connection limitStable, controlled concurrency

πŸ’‘ In high-traffic APIs, pooling can make the difference between handling 100 requests/second vs. 5 requests/second.

⚑ 7. Summary of Relationships

ComponentPurpose
SQLAlchemyAbstraction layer for relational DBs
ORMMaps Python classes to database tables
create_engine()Creates engine + connection pool
SessionTemporary workspace that borrows a connection
Connection PoolKeeps connections ready for reuse
FastAPI RequestCreates a new session for each request, which uses the pool

🧩 8. The Full Flow (Visual Diagram)

Here’s a simplified diagram showing how these pieces connect:

+------------------+
|  API Request     |
+--------+---------+
         |
         v
+------------------+
|  get_db()       |  ← creates session
+--------+---------+
         |
         v
+------------------+
|  SessionLocal()  |  ← borrows connection from pool
+--------+---------+
         |
         v
+------------------+
|  Connection Pool |  ← created & managed by SQLAlchemy engine
+--------+---------+
         |
         v
+------------------+
|   Database       |
+------------------+

Would you like me to generate this as a high-quality image diagram (with colors, arrows, and labels) that you can save and reuse in your notes?

🧩 9. Understanding SessionLocal, Session, and Connection Pools

When using SQLAlchemy with FastAPI, database communication happens through three layers:

create_engine() β†’ SessionLocal() β†’ Session (per request)

βš™οΈ 1. create_engine() β€” Global Engine & Connection Pool

create_engine() creates the engine that knows how to talk to the database and also initializes a global connection pool.
This pool keeps a small number of active database connections ready for reuse.

engine = create_engine(
    "postgresql://user:password@localhost/mydb",
    pool_size=10,
    max_overflow=20
)

🧰 2. SessionLocal β€” The Session Factory

βœ… SessionLocal is not an actual session β€” it’s a session factory (a kind of constructor or blueprint) created using sessionmaker(). You use it to create new Session objects whenever needed (for each API request). It doesn’t connect to the database directly β€” it just knows how to create a session that connects using the existing engine (and therefore, the existing connection pool).

SessionLocal is a factory that produces individual Session objects. It’s bound to the global engine (and thus to its connection pool):

SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)

It doesn’t hold connections itself β€” it just defines how sessions are created.


πŸ”„ 3. Session β€” Per-Request Database Session

The SQLAlchemy session exists only for database operations β€” things like reading, writing, updating, or deleting records.

βœ… When a session is used

  • When your API endpoint queries data from the DB (e.g., db.query(models.Todo).all())
  • When you add, update, or delete ORM objects
  • When FastAPI uses Depends(get_db) to inject the session

❌ When a session is NOT needed

  • When serving static files (like HTML, CSS, or images)
  • When returning computed values or text responses
  • When calling external APIs (like fetching weather data)
  • When doing authentication/token verification (if stored outside DB)

So β€” you only create a session when your route talks to the database.

FastAPI is smart: it doesn’t automatically use a session for all requests β€” you control it with Depends(get_db) only in routes that need it. Each API request gets its own Session, created by calling SessionLocal() inside a dependency:

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
  • Each Session borrows a single connection from the shared pool.
  • Once the request completes, the session is closed and the connection is returned to the pool.
SessionLocal β†’ (factory)
Session = SessionLocal() β†’ (actual session that borrows connection from pool)

Think of it like:

🧱 SessionLocal = class definition
🧍 SessionLocal() = creating an object (session instance)

βš™οΈ sessionmaker() β€” The Function That Creates the Factory

sessionmaker() is a SQLAlchemy helper function that returns a customized Session class with your preferred settings.

Example:

from sqlalchemy.orm import sessionmaker

SessionLocal = sessionmaker(
bind=engine,
autoflush=False,
autocommit=False
)

πŸ” Explanation of Parameters

ParameterMeaning
bind=engineTells the session which database engine (and its connection pool) to use. Without it, the session wouldn’t know where to connect.
autoflush=FalsePrevents automatic flushing (sending pending changes to DB) before every query. You control when to flush/commit β€” gives more predictability.
autocommit=FalseEnsures SQLAlchemy uses transaction mode β€” you must explicitly call db.commit(). This avoids accidental partial saves.

🧠 In short:

SessionLocal = sessionmaker(bind=engine, ...)
β†’ creates a factory that knows how to build new Sessions
β†’ each Session borrows a connection from the engine’s pool,
β†’ runs queries, commits/rolls back, then returns the connection when closed.


πŸ” Summary

ComponentScopePurpose
create_engine()GlobalCreates engine and manages the connection pool
Connection PoolGlobal (inside engine)Holds a set of open database connections
SessionLocalGlobalFactory for creating new Session objects
SessionPer requestBorrows and returns connections from the pool

In short:

  1. The engine owns the connection pool.
  2. SessionLocal is a global session factory.
  3. Each API request creates its own temporary Session, which borrows a connection from the shared pool.

Scroll to Top