FastAPI: From Basics to Production · Databases with SQLAlchemy 2.0 and Alembic
Setting Up Async SQLAlchemy
Engine, session factory, declarative models and per-request sessions.
SQLAlchemy 2.0 supports asyncio via create_async_engine (e.g., postgresql+asyncpg://) and async_sessionmaker. Define models with typed Mapped[...] annotations and mapped_column. Use expire_on_commit=False so objects remain usable after commit in async code, configure pool size per worker, and create one session per request via a yield dependency.
from datetime import datetime
from sqlalchemy import ForeignKey, String, func
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
engine = create_async_engine(settings.database_url, pool_size=10, max_overflow=5, pool_pre_ping=True)
SessionLocal = async_sessionmaker(engine, expire_on_commit=False)
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
email: Mapped[str] = mapped_column(String(320), unique=True, index=True)
created_at: Mapped[datetime] = mapped_column(server_default=func.now())
orders: Mapped[list["Order"]] = relationship(back_populates="user")
class Order(Base):
__tablename__ = "orders"
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
total_cents: Mapped[int]
user: Mapped[User] = relationship(back_populates="orders")
Common pitfalls
- Sharing one AsyncSession across concurrent tasks — sessions aren't concurrency-safe.
- Default
expire_on_commit=Truecausing lazy reloads (errors in async) after commit. - Pool size × workers exceeding the database's max connections.
Key points
- create_async_engine + async_sessionmaker
- Typed Mapped models
- One session per request/task
Further reading: SQLAlchemy: asyncio extension
Practice questions
Why set expire_on_commit=False for async sessions?
Answer
With expiry, attributes are reloaded on next access after commit, which requires implicit I/O that async sessions don't allow (MissingGreenlet errors).
How do you size the connection pool?
Answer
Consider pool_size + max_overflow per process times the number of processes; keep the total below the database's connection limit (or use PgBouncer).
Open in the app to mark lessons complete, take notes and track your progress.