Concept lesson

FastAPI + SQLAlchemy 2.0 Async Engine

AsyncEngine configuration, async_sessionmaker, AsyncSession lifecycle management, and AsyncPG driver integration.

lesson
Freshness: current15 min read
Mastery
not started · 0%

Learning outcomes

  • Configure async connection pools with asyncpg
  • Prevent session leaks across concurrent FastAPI routes

Mental model

SQLAlchemy 2.0 introduces native async support over AsyncIO drivers (asyncpg). Combining FastAPI dependency injection with AsyncSession guarantees clean connection acquisition and teardown on every request tick.

HTTP Request Route
FastAPI Depends(get_async_session)
Acquire AsyncSession from async_sessionmaker
Execute async SQL with await session.execute()
Yield Teardown & Commit/Rollback
Conceptual teaching model synthesized from:FastAPI Framework Architecture & Dependency Injection Specification

Theory

When running async route handlers (async def), executing blocking synchronous ORM calls (session.query()) blocks Python's single-threaded event loop. AsyncEngine combined with asyncpg offloads database socket I/O to non-blocking epoll events.

from typing import AsyncGenerator
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import select
from fastapi import FastAPI, Depends

DATABASE_URL = "postgresql+asyncpg://user:pass@localhost:5432/app_db"

engine = create_async_engine(DATABASE_URL, pool_size=20, max_overflow=10)
AsyncSessionFactory = async_sessionmaker(engine, expire_on_commit=False)

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "users"
    id: Mapped[int] = mapped_column(primary_key=True)
    username: Mapped[str]

async def get_db_session() -> AsyncGenerator[AsyncSession, None]:
    async with AsyncSessionFactory() as session:
        try:
            yield session
            await session.commit()
        except Exception:
            await session.rollback()
            raise

app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(user_id: int, db: AsyncSession = Depends(get_db_session)):
    result = await db.execute(select(User).where(User.id == user_id))
    user = result.scalar_one_or_none()
    return {"user": user.username if user else None}

Alternatives and trade-offs

  • Synchronous ORM (psycopg2): Simple; blocks AsyncIO event loop unless wrapped in run_in_executor.
  • SQLAlchemy 2.0 Async (asyncpg): High concurrency non-blocking I/O; requires explicit await on all DB operations and lazy loading handling.

Failure modes and misconceptions

  1. Implicit Lazy Loading Crashes: Accessing un-loaded ORM relationships (user.orders) in AsyncIO raises MissingGreenlet error. Use explicit joined loads (options(joinedload(User.orders))).
  2. expire_on_commit=True Overhead: Default SQLAlchemy setting expires object attributes on commit, forcing secondary async DB fetches when accessing object attributes after commit. Always set expire_on_commit=False.
Reflect before revealing the guide

Decision scenario

Always set expire_on_commit=False on async_sessionmaker and use explicit eager loading (joinedload/selectinload) to avoid MissingGreenlet errors in FastAPI async routes.

Learning outcomes

  • Build non-blocking FastAPI database integration with SQLAlchemy 2.0 and asyncpg.
  • Manage AsyncSession lifecycles safely using FastAPI dependency injection.
  • Prevent MissingGreenlet errors using explicit eager loading strategies.

Trade-offs

SQLAlchemy 2.0 Async delivers massive concurrency under high HTTP load, but requires disciplined handling of relationship loading and explicit session scopes.

Evidence assessment

Theory and decision mastery

not-started · 0%
theory0%
decision0%
activityNot mapped
projectNot mapped
1. Why should FastAPI async endpoints use SQLAlchemy 2.0 AsyncEngine with asyncpg drivers instead of psycopg2?
2. What causes a MissingGreenlet error in SQLAlchemy 2.0 AsyncSession execution?
3. Why is expire_on_commit=False recommended for SQLAlchemy async_sessionmaker instances?

Decision scenario

A FastAPI application experiences occasional database connection pool exhaustion under high concurrent load due to unclosed sessions.

Which dependency pattern guarantees clean AsyncSession closing even if route handlers raise uncaught exceptions?

Primary sources