FastAPI: From Basics to Production · Databases with SQLAlchemy 2.0 and Alembic

CRUD with select(), Sessions and Transactions

Query and modify data with SQLAlchemy 2.0 style.

SQLAlchemy 2.0 uses select() statements executed with session.execute()/session.scalars(). Add objects with session.add(), flush() to get generated IDs, and commit once per unit of work. Use session.get() for primary-key lookups, and bulk update()/delete() statements for set-based changes. Keep queries in repository/service functions.

from sqlalchemy import select, update


async def get_user(db: AsyncSession, user_id: int) -> User | None:
    return await db.get(User, user_id)


async def list_orders(db: AsyncSession, user_id: int, limit: int, after_id: int | None) -> list[Order]:
    stmt = select(Order).where(Order.user_id == user_id).order_by(Order.id.desc()).limit(limit)
    if after_id is not None:
        stmt = stmt.where(Order.id < after_id)
    return list(await db.scalars(stmt))


async def create_order(db: AsyncSession, user_id: int, total_cents: int) -> Order:
    order = Order(user_id=user_id, total_cents=total_cents)
    db.add(order)
    await db.flush()          # assigns order.id; commit happens in the session dependency
    return order


async def deactivate_stale(db: AsyncSession) -> int:
    result = await db.execute(update(User).where(User.last_login < cutoff).values(active=False))
    return result.rowcount

Common pitfalls

  • Committing inside every helper, breaking atomicity of multi-step operations.
  • Loading all rows then filtering in Python.
  • Using legacy session.query() style in new code.

Key points

  • select() + scalars()
  • flush for IDs, commit once per unit of work
  • Set-based updates for bulk changes

Further reading: SQLAlchemy: ORM querying guide

Practice questions

What's the difference between flush() and commit()?

Answer

flush() sends pending changes to the database within the current transaction (e.g., to get IDs); commit() ends the transaction and makes changes permanent.

Why not commit inside each repository function?

Answer

Multi-step operations would be partially committed if a later step fails; one commit per request/unit of work keeps them atomic.

Open in the app to mark lessons complete, take notes and track your progress.