Database

Forge generates a complete database layer with async support, connection pooling, and migration management.

Supported Databases

Database

Driver

Best For

PostgreSQL

asyncpg

Production environments

MySQL

aiomysql

Production environments

SQLite

aiosqlite

Development and testing

ORM Options

SQLModel

SQLModel combines SQLAlchemy Core with Pydantic models. It’s simpler and integrates well with FastAPI:

from sqlmodel import Field, SQLModel

class User(SQLModel, table=True):
    __tablename__ = "users"
    
    id: Optional[int] = Field(default=None, primary_key=True)
    username: str = Field(unique=True, index=True)
    email: str = Field(unique=True, index=True)

SQLAlchemy

SQLAlchemy provides more flexibility and advanced features:

from sqlalchemy import Column, Integer, String
from app.core.database import Base

class User(Base):
    __tablename__ = "users"
    
    id = Column(Integer, primary_key=True, index=True)
    username = Column(String(50), unique=True, index=True)
    email = Column(String(100), unique=True, index=True)

Database Connection

Connection Manager

For PostgreSQL and MySQL, Forge generates a DatabaseConnectionManager in app/core/database/connection.py:

class DatabaseConnectionManager:
    async def initialize(self) -> None:
        """Initialize database connections"""
        await self.postgresql_manager.initialize()
    
    async def test_connections(self) -> bool:
        """Test database connectivity"""
        await self.postgresql_manager.test_connection()
        return True
    
    async def close(self) -> None:
        """Close all connections"""
        await self.postgresql_manager.close()

Getting a Session

Use the get_db dependency in your routes:

from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db

@router.get("/users")
async def list_users(db: AsyncSession = Depends(get_db)):
    # Use db session here
    pass

Connection Pool Settings

Configure the connection pool via environment variables:

ECHO=false              # Log SQL queries
POOL_PRE_PING=true      # Test connections before use
POOL_TIMEOUT=30         # Connection timeout in seconds
POOL_SIZE=6             # Number of connections to maintain
POOL_MAX_OVERFLOW=2     # Extra connections allowed

Database Migrations

Forge uses Alembic for database migrations with async support.

Configuration

The alembic/env.py file is configured to:

  • Load database URL from environment variables

  • Use async database drivers

  • Auto-import all models for autogenerate

Creating Migrations

Generate a migration after changing models:

alembic revision --autogenerate -m "Add new field"

Review the generated migration in alembic/versions/ before applying.

Applying Migrations

Apply all pending migrations:

alembic upgrade head

Apply one migration at a time:

alembic upgrade +1

Rolling Back

Rollback one migration:

alembic downgrade -1

Rollback to a specific revision:

alembic downgrade <revision_id>

Rollback all migrations:

alembic downgrade base

Viewing History

# Current revision
alembic current

# Migration history
alembic history

# Detailed history
alembic history --verbose

Adding New Models

Step 1: Create the Model

Create a new file in app/models/:

# app/models/post.py
from datetime import datetime
from typing import Optional
from sqlmodel import Field, SQLModel

class Post(SQLModel, table=True):
    __tablename__ = "posts"
    
    id: Optional[int] = Field(default=None, primary_key=True)
    title: str = Field(max_length=200)
    content: str
    author_id: int = Field(foreign_key="users.id")
    created_at: datetime = Field(default_factory=datetime.utcnow)

Step 2: Import in env.py

Ensure the model is imported in alembic/env.py:

# Import all models so Alembic can detect them
from app.models.user import User
from app.models.post import Post  # Add this

Step 3: Generate Migration

alembic revision --autogenerate -m "Add posts table"

Step 4: Apply Migration

alembic upgrade head

CRUD Operations

Forge generates CRUD classes for authentication models. Follow the same pattern for new models:

# app/crud/post.py
from typing import List, Optional
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.models.post import Post

class PostCRUD:
    @staticmethod
    async def get_by_id(db: AsyncSession, post_id: int) -> Optional[Post]:
        return await db.get(Post, post_id)
    
    @staticmethod
    async def get_all(db: AsyncSession, skip: int = 0, limit: int = 100) -> List[Post]:
        statement = select(Post).offset(skip).limit(limit)
        result = await db.execute(statement)
        return list(result.scalars().all())
    
    @staticmethod
    async def create(db: AsyncSession, post: Post) -> Post:
        db.add(post)
        await db.commit()
        await db.refresh(post)
        return post

post_crud = PostCRUD()

Docker Database Setup

When Docker is enabled, docker-compose.yml includes the database service:

PostgreSQL:

db:
  image: postgres:15-alpine
  environment:
    - POSTGRES_USER=postgres
    - POSTGRES_PASSWORD=postgres
    - POSTGRES_DB=my_project
  ports:
    - "5432:5432"
  volumes:
    - postgres_data:/var/lib/postgresql/data

MySQL:

db:
  image: mysql:8.0
  environment:
    - MYSQL_ROOT_PASSWORD=mysql
    - MYSQL_DATABASE=my_project
  ports:
    - "3306:3306"
  volumes:
    - mysql_data:/var/lib/mysql

The db-migrate service automatically runs migrations on startup:

db-migrate:
  build: .
  command: sh -c "alembic revision --autogenerate -m 'Auto migration' && alembic upgrade head"
  depends_on:
    db:
      condition: service_healthy