Skip to main content

Overview

SmartEat AI uses Alembic for database migrations. Alembic tracks schema changes through version-controlled migration scripts, allowing you to evolve the database structure safely across environments.

Prerequisites

Before working with migrations, ensure:
  1. PostgreSQL database is running
  2. Database connection string is configured in .env
  3. All model changes are committed to SQLAlchemy models in backend/app/models/

Configuration

Alembic configuration is defined in two files:

alembic.ini

Located at backend/alembic.ini, this file contains Alembic settings:
[alembic]
script_location = alembic
prepend_sys_path = .
version_path_separator = os

env.py

Located at backend/alembic/env.py, this file configures how Alembic connects to your database:
from app.database import Base
from app.config import settings
from app.models import *  # Import all models

config.set_main_option('sqlalchemy.url', settings.DATABASE_URL)
target_metadata = Base.metadata
The DATABASE_URL is automatically read from your environment variables, so you don’t need to hardcode database credentials.

Common Migration Commands

View Migration History

Check the current migration state and history:
alembic history
View the current version:
alembic current

Creating New Migrations

When you modify database models, create a new migration:
1

Modify your models

Edit the SQLAlchemy models in backend/app/models/. For example, adding a field to the User model:
# backend/app/models/user.py
class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)
    email = Column(String, unique=True, nullable=False)
    phone_number = Column(String)  # New field
2

Generate migration

Use Alembic’s autogenerate feature to detect changes:
alembic revision --autogenerate -m "add phone_number to users"
This creates a new migration file in backend/alembic/versions/ with a revision ID and timestamp.
3

Review the migration

Open the generated file in backend/alembic/versions/ and verify the changes:
def upgrade() -> None:
    op.add_column('users', sa.Column('phone_number', sa.String(), nullable=True))

def downgrade() -> None:
    op.drop_column('users', 'phone_number')
Always review autogenerated migrations. Alembic may miss some changes or generate suboptimal SQL. Edit the migration if needed.
4

Apply the migration

Run the migration to update your database:
alembic upgrade head

Applying Migrations

Upgrade to the latest version:
alembic upgrade head
Upgrade to a specific revision:
alembic upgrade <revision_id>
Upgrade by a relative number of revisions:
alembic upgrade +2

Rolling Back Migrations

Downgrade one migration:
alembic downgrade -1
Downgrade to a specific revision:
alembic downgrade <revision_id>
Downgrade to base (empty database):
alembic downgrade base
Be careful with downgrades in production! Rolling back migrations can result in data loss, especially if columns or tables are dropped.

Migration File Structure

Migration files follow this structure:
"""add phone_number to users

Revision ID: abc123def456
Revises: previous_revision_id
Create Date: 2026-03-05 10:30:00.000000
"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic
revision = 'abc123def456'
down_revision = 'previous_revision_id'
branch_labels = None
depends_on = None

def upgrade() -> None:
    # Commands to apply changes
    op.add_column('users', 
        sa.Column('phone_number', sa.String(), nullable=True))

def downgrade() -> None:
    # Commands to revert changes
    op.drop_column('users', 'phone_number')

Manual Migrations

For complex schema changes, create a blank migration and write custom logic:
alembic revision -m "custom migration description"
Then edit the generated file with custom SQL or Alembic operations:
def upgrade() -> None:
    # Custom migration logic
    op.execute("""
        UPDATE users 
        SET email = LOWER(email)
        WHERE email != LOWER(email)
    """)

def downgrade() -> None:
    # Revert logic (if possible)
    pass

Common Operations

Adding a Column

def upgrade():
    op.add_column('table_name', 
        sa.Column('column_name', sa.String(), nullable=True))

def downgrade():
    op.drop_column('table_name', 'column_name')

Creating a Table

def upgrade():
    op.create_table(
        'new_table',
        sa.Column('id', sa.Integer(), primary_key=True),
        sa.Column('name', sa.String(), nullable=False),
        sa.Column('created_at', sa.TIMESTAMP(), nullable=False)
    )

def downgrade():
    op.drop_table('new_table')

Adding an Index

def upgrade():
    op.create_index('ix_users_email', 'users', ['email'])

def downgrade():
    op.drop_index('ix_users_email', 'users')

Modifying a Column

def upgrade():
    op.alter_column('users', 'email',
                    existing_type=sa.String(),
                    nullable=False)

def downgrade():
    op.alter_column('users', 'email',
                    existing_type=sa.String(),
                    nullable=True)

Best Practices

Important migration guidelines:
  1. Always review autogenerated migrations before applying
  2. Test migrations in development before production
  3. Keep migrations small and focused on single changes
  4. Never edit applied migrations - create new ones instead
  5. Write descriptive migration messages
  6. Always implement both upgrade() and downgrade() functions
  7. Backup your database before running migrations in production

Troubleshooting

Migration out of sync

If Alembic reports the database is out of sync:
# Stamp the current revision without running migrations
alembic stamp head

Multiple heads detected

If you have branching migration history:
# View all heads
alembic heads

# Merge branches
alembic merge <rev1> <rev2> -m "merge migrations"

Reset database completely

To start fresh (development only):
1

Downgrade to base

alembic downgrade base
2

Upgrade to latest

alembic upgrade head

Migration Workflow

1

Make model changes

Edit your SQLAlchemy models in backend/app/models/
2

Generate migration

alembic revision --autogenerate -m "describe your changes"
3

Review migration file

Open the generated file in backend/alembic/versions/ and verify the SQL operations
4

Apply migration

alembic upgrade head
5

Test your changes

Run your application and verify the schema changes work correctly
6

Commit migration to git

git add backend/alembic/versions/<new_migration_file>.py
git commit -m "Add migration: describe your changes"

See Also

Build docs developers (and LLMs) love