1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

🚀 FastAPI × SQLAlchemy × Alembic で create_all で作ったテーブルを Alembic 管理へ移行する

1
Posted at

🚀 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 を卒業し、本格的なマイグレーション運用に移行できます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?