FastAPI handles HTTP requests, while SQLAlchemy manages database operations. Together, they provide a practical way to build database-driven APIs using Python and PostgreSQL.
This guide covers asynchronous database configuration, ORM models, dependency injection, and database migrations with Alembic.
pip install fastapi uvicorn "sqlalchemy[asyncio]" asyncpg alembic
Create app/database.py to configure the database engine, session factory, and dependency.
import osfrom collections.abc import AsyncGeneratorfrom sqlalchemy.ext.asyncio import (AsyncSession,async_sessionmaker,create_async_engine,)DATABASE_URL = os.environ["DATABASE_URL"]engine = create_async_engine(DATABASE_URL)AsyncSessionLocal = async_sessionmaker(engine,expire_on_commit=False,)async def get_db() -> AsyncGenerator[AsyncSession, None]:async with AsyncSessionLocal() as session:yield session
Configure DATABASE_URL through your environment, for example:
postgresql+asyncpg://user:password@localhost:5432/plant_shop
Key components:
get_db(): Provides a session to the request and closes it when the dependency finishes.The session context manager handles cleanup, but it does not automatically commit transactions.
A SQLAlchemy model maps a Python class to a database table.
Create app/models.py:
from decimal import Decimalfrom sqlalchemy import Integer, Numeric, Stringfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_columnclass Base(DeclarativeBase):passclass Plant(Base):__tablename__ = "plants"id: Mapped[int] = mapped_column(primary_key=True)name: Mapped[str] = mapped_column(String(100))price: Mapped[Decimal] = mapped_column(Numeric(10, 2))stock: Mapped[int] = mapped_column(Integer, default=0)
Mapped and mapped_column() provide SQLAlchemy 2.x’s typed ORM mapping syntax.
Use Decimal with Numeric for monetary values to avoid floating-point precision issues.
Keep SQLAlchemy models separate from Pydantic schemas. ORM models represent database tables, while Pydantic schemas validate API inputs and define response structures.
Use Depends() to inject a database session into an endpoint.
Create app/main.py:
from fastapi import Depends, FastAPIfrom sqlalchemy import selectfrom sqlalchemy.ext.asyncio import AsyncSessionfrom app.database import get_dbfrom app.models import Plantapp = FastAPI()@app.get("/plants")async def get_plants(db: AsyncSession = Depends(get_db),):result = await db.execute(select(Plant))return result.scalars().all()
Here’s what happens:
get_db() and provides an AsyncSession.scalars().all() extracts the returned Plant objects.For production APIs, define a Pydantic response schema with response_model rather than returning ORM objects directly.
Use the session to add a record and persist it to the database.
plant = Plant(name="Monstera",price=Decimal("250000.00"),stock=10,)db.add(plant)await db.commit()await db.refresh(plant)
db.add(): Adds the object to the session.await db.commit(): Commits the transaction.await db.refresh(): Reloads database-generated values, such as the assigned ID.Unlike execute() and commit(), add() is synchronous because it stages the object in the session rather than performing an immediate database operation.
In production code, handle failures and roll back an active transaction when necessary.
Database models can change as an application evolves. Alembic tracks schema changes through versioned migration scripts.
Initialize an asynchronous migration environment:
alembic init -t async migrations
Configure Alembic’s target_metadata to use Base.metadata and ensure the migration environment can import your models.
Generate and apply a migration:
alembic revision --autogenerate -m "create plants"alembic upgrade head
revision --autogenerate: Generates a migration based on differences between your models and database schema.upgrade head: Applies migrations up to the latest revision.Always review autogenerated migrations before applying them. Alembic does not detect every schema change automatically, and model definitions alone do not create or update database tables.
Combining FastAPI, SQLAlchemy 2.x, and Alembic provides a maintainable foundation for asynchronous PostgreSQL APIs.