The database layer provides async PostgreSQL connectivity using SQLAlchemy 2.0 with asyncpg driver. It implements connection pooling, session management, and proper async context manager patterns.
- DatabaseSessionManager: Singleton session manager with connection pooling
- AsyncSession: SQLAlchemy async session for database operations
- Base: Declarative base for ORM models
- get_db(): Dependency injection function for FastAPI routes
Database settings are managed through Pydantic Settings in app/core/config.py:
class Settings(BaseSettings):
DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/fastapi"Environment variables:
DATABASE_URL: PostgreSQL connection string with asyncpg driver
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
@router.get("/users")
async def get_users(db: AsyncSession = Depends(get_db)):
result = await db.execute(select(User))
return result.scalars().all()from app.core.database import sessionmanager
async with sessionmanager.session() as session:
result = await session.execute(select(User))
users = result.scalars().all()import pytest
from sqlalchemy.ext.asyncio import AsyncSession
@pytest.mark.asyncio
async def test_database_operation(db_session: AsyncSession):
# db_session fixture provided by conftest.py
result = await db_session.execute(select(User))
assert result is not None- Initialization:
sessionmanager.init(DATABASE_URL)creates engine and session factory - Session Creation:
async with sessionmanager.session()provides isolated session - Auto-commit: Sessions commit automatically on successful context exit
- Auto-rollback: Sessions rollback on exceptions
- Cleanup:
await sessionmanager.close()closes all connections
The session() method uses @asynccontextmanager decorator:
from contextlib import asynccontextmanager
@asynccontextmanager
async def session(self) -> AsyncIterator[AsyncSession]:
if self._sessionmaker is None:
raise Exception("DatabaseSessionManager is not initialized")
session = self._sessionmaker()
try:
yield session
except Exception:
await session.rollback()
raise
finally:
await session.close()SQLAlchemy's connection pool is configured in app/core/database.py:
- pool_size: Number of persistent connections
- max_overflow: Additional connections when pool exhausted
- pool_pre_ping: Verify connections before use (enabled)
Tests use a separate test database configured in tests/conftest.py:
TEST_DATABASE_URL = "postgresql+asyncpg://postgres:postgres@localhost:5432/fastapi_test"db_session: Provides clean AsyncSession for each testtest_db: Manages test database lifecycle (create/drop tables)
# Run all database tests
poetry run pytest tests/core/test_database.py -v
# Run with coverage
poetry run pytest tests/core/test_database.py --cov=app.core.databaseDatabase migrations are managed with Alembic (async configuration).
# Create new migration
alembic revision --autogenerate -m "description"
# Apply migrations
alembic upgrade head
# Rollback one migration
alembic downgrade -1
# Show current revision
alembic currentAlembic is configured for async operations in alembic/env.py:
- Uses
asyncpgdriver - Imports
Basemetadata from models - Runs migrations in async context
-
Connection Refused
- Ensure PostgreSQL is running:
sudo systemctl status postgresql - Verify connection string in
.env
- Ensure PostgreSQL is running:
-
Session Not Initialized
- Call
sessionmanager.init(DATABASE_URL)before use - Check application startup in
app/main.py
- Call
-
Async Context Manager Error
- Ensure
@asynccontextmanagerdecorator is present - Use
async withfor session context
- Ensure
-
Pool Exhausted
- Increase
DB_POOL_SIZEorDB_MAX_OVERFLOW - Check for unclosed sessions in code
- Increase
- Always use dependency injection (
Depends(get_db)) in routes - Never store sessions as instance variables
- Use transactions for multi-step operations
- Close sessions explicitly in non-FastAPI contexts
- Use connection pooling for production deployments
- Separate test and production databases
- Run migrations before deploying new versions
- Connection pooling reduces overhead of creating new connections
pool_pre_pingadds small latency but prevents stale connections- Async operations allow handling multiple requests concurrently
- Use
selectinload()orjoinedload()to avoid N+1 queries
- Never commit
.envfiles with production credentials - Use environment variables for sensitive configuration
- Implement connection encryption for production (SSL/TLS)
- Use read-only database users for reporting queries
- Regularly rotate database passwords
Key metrics to monitor:
- Connection pool utilization
- Query execution time
- Failed connection attempts
- Active session count
- Database CPU and memory usage