🚀 FastAPI × SQLAlchemy × Alembic を“ゼロから”導入する手順
この記事では FastAPI × SQLAlchemy で DB を扱う際に必須となる「Alembic」 を、
ゼロの状態から導入し、既存テーブルをそのままマイグレーション管理に移行する までを丁寧に説明します。
こちらの記事で作成したアプリをベースに話を進めますので、もしよければ一読ください。
👇 本記事でできるようになること
- FastAPI に Alembic を 新規導入できる
- SQLAlchemy の Base.metadata を Alembic に正しく認識させる
- create_all で作ったテーブルを壊さず、そのまま初期マイグレーションに登録できる
- カラム追加(例:created_at / updated_at)を マイグレーションで安全に反映できる
- create_all を 卒業して Alembic のみで DB 管理できる
🏗 想定プロジェクト構成
backend/
└ src/
├ main.py
├ models.py
├ database/
│ └ database.py
まだ Alembic は導入していない状態です。
テーブルは Base.metadata.create_all(bind=engine) で作られている前提。
1. Alembic をインストールする
まず Alembic を追加します。
pip install alembic
or Docker なら
pip install alembic python-dotenv
2. Alembic プロジェクトを初期化する
FastAPI の src ディレクトリで以下を実行:
cd src
alembic init migrations
すると以下が生成されます:
src/migrations/
├ versions/ ← マイグレーションファイルが入る
├ env.py ← Alembic の設定スクリプト(超重要)
└ README
src/alembic.ini ← Alembic 全体設定
3. env.py を FastAPI/SQLAlchemy 用に設定する(最重要ポイント)
Alembic を FastAPI と連携させるには、
- SQLAlchemy の Base
- モデル(Todo など)
- DB URL
を Alembic に教える必要があります。
🔧 env.py の修正(必要部分のみ)
src/migrations/env.py
from logging.config import fileConfig
from alembic import context
from sqlalchemy import engine_from_config, pool
from dotenv import load_dotenv
load_dotenv()
# ★ アプリ内の Base と DB URL を読み込む
from database.database import SQLALCHEMY_DATABASE_URL
import models # ← Todoなどのモデル定義を読み込む
config = context.config
# ★ Alembic の sqlalchemy.url をアプリと同じ値に上書き
config.set_main_option("sqlalchemy.url", SQLALCHEMY_DATABASE_URL)
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# ★ Alembic が使う metadata
target_metadata = models.Base.metadata
※ run_migrations_online/offline は自動生成のままで OK。
🔍 なぜ models を import するの?
Alembic は target_metadata に渡した Base.metadata の中にある
- テーブル
- カラム
- 制約
を “モデルの定義” として扱います。
import models を忘れると、
モデルが読み込まれず、metadata にテーブルが登録されません。
その結果:
Detected removed table 'todos'
などと出て テーブルが DROP される事故が起きます。
4. Base.metadata にちゃんとテーブルが載っているか確認する(必須)
Alembic が正しく動くかどうかは まず metadata の中身を見るとわかります。
以下を実行:
python - << 'PY'
import models
print(models.Base.metadata.tables.keys())
PY
出力:
dict_keys(['todos'])
のように todos が含まれていれば OK。
もし空なら
→ models が読み込めていない
→ Base を別で作っている etc
なので修正が必要です。
5. 現在の DB を「初期状態」として Alembic に登録する
ここが create_all → Alembic 移行の最大のポイントです。
(1) まず create_all で作られた今のテーブルを SQLAlchemy モデルと一致させる
例:
class Todo(Base):
__tablename__ = "todos"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(255), nullable=False)
done = Column(Boolean, nullable=False, default=False)
この状態を “初期スキーマ” とします。
まだ timestamps は入れないでください。
(2) init マイグレーションを生成する
alembic revision --autogenerate -m "init"
ログが 差分なし であれば OK。
(差分が出る場合 → モデルと DB が一致していないので直す)
(3) DB を init リビジョンとして stamp する
今の DB は create_all によってすでに作成済みなので、
upgrade は不要です。
alembic stamp head
これで:
「今の DB の状態は init マイグレーションの状態である」
と Alembic が認識します。
ただし、別環境で一から構築する際には、initリビジョンのファイルは空なので現状のDB構成を作成するようにinitマイグレーションファイルを編集しておいてください。(後続のupgrade時に他の環境だとテーブルが存在しないってエラーが出ます、、)
"""init
Revision ID: 4fd61e8ba5b6
Revises:
Create Date: 2025-11-20 09:51:25.206090
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '4fd61e8ba5b6'
down_revision: Union[str, Sequence[str], None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"todos",
sa.Column("id", sa.Integer, primary_key=True),
sa.Column("title", sa.String(255), nullable=False),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("is_done", sa.Boolean(), nullable=False, server_default=sa.text("0")),
)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("todos")
# ### end Alembic commands ###
6. 以後 create_all を削除する(必須)
もう DB を作るのは Alembic の仕事になるので
main.py の create_all を削除します。
@asynccontextmanager
async def lifespan(app):
yield
これで FastAPI 起動時に DB が勝手に変更されなくなり、
本番運用に耐えられる構成になります。
7. created_at / updated_at をマイグレーションで追加する
(1) モデルにカラム追加
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
(2) 差分マイグレーション生成
alembic revision --autogenerate -m "add timestamps"
ログ:
Detected added column 'created_at'
Detected added column 'updated_at'
(3) 適用
alembic upgrade head
DESC todos; で 2 カラム追加されていれば成功 🎉
🎉 まとめ
今回の流れをまとめると:
| 手順 | 内容 |
|---|---|
| 1 | Alembic を新規導入(alembic init) |
| 2 | env.py を FastAPI 用に修正 |
| 3 | Base.metadata にテーブルが載るか確認 |
| 4 | init マイグレーション作成(差分なし) |
| 5 | DB を stamp |
| 6 | create_all を削除 |
| 7 | モデル変更 → autogenerate → upgrade(マイグレーション運用開始) |
このステップにより create_all を卒業し、本格的なマイグレーション運用に移行できます。