こんにちは、ナナオです。
前回、KoyebでDiscord botを構築してみました。
今回はこれに無料で使えると話題のNeonを組み合わせてみようと思います。
Neonとは
Neonはサーバーレスで動くPostgresのサービスです。
Neon Serverless Postgres — Ship faster
DBのブランチ戦略やオートスケーリングに長けており、なにより無料で使えるプランがあるのが個人開発者には魅力的な点です。
アカウント登録 ~ プロジェクト作成
まずはNeonにサインアップします。
サインアップしたらプロジェクトの作成画面に遷移します。
プロジェクト名を入力し、リージョンは日本がないので、とりあえずデフォルトのN. Virginiaにしておきます。
プロジェクトが作成されました。
NeonのDBに接続してみます。
Webコンソール上からアクセスしてみましょう。
左側のタブからTablesを選択します。
neondbというDBがあるのと、publicスキーマがあるだけで、テーブルは何もありません。
マイグレーション管理(alembic)の導入
discord botの実装にはpythonを使用しているので、マイグレーションにもpythonを使用します。
まずはプロジェクトにSQLAlchemyとalembicを導入します。
uv add SQLAlchemy alembic psycopg2
alembicでマイグレーション用のディレクトリを作成します。
uv run alembic init migrations
作成されたalembic.iniのデータベースURLの設定を、環境変数を参照するように書き換えます。
# database URL. This is consumed by the user-maintained env.py script only.
# other means of configuring database URLs may be customized within the env.py
# file.
sqlalchemy.url = %(DATABASE_URL)s
.envにはDATABASE_URL環境変数を設定します。
NeonのプロジェクトダッシュボードからConnectをクリックし、表示されたconnection stringをコピーします。
env.pyの冒頭を以下のように修正します。
import os
from logging.config import fileConfig
from alembic import context
from dotenv import load_dotenv
from sqlalchemy import engine_from_config, pool
load_dotenv()
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# この行を追加!
config.set_section_option("alembic", "DATABASE_URL", os.environ["DATABASE_URL"])
# ...中略...
マイグレーションの土台は完成しました。
モデルを実装していきます。
(ちなみに使っているSQLAlchemyは2.0です)
以下のようにアプリケーションのディレクトリを構築します。
└── src
└── mokumoku_bot
├── db
│ └── __init__.py
│ └── base.py
└── model
└── history.py
base.pyの実装は以下の通り。
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
...
モデルの実装は公式のQuickStartを参考に実装します。
ORM Quick Start — SQLAlchemy 2.0 Documentation
今回はbotの特定コマンドを実行したユーザーと日時を持つテーブルを作成します。
import datetime as dt
from typing import Literal
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column
from mokumoku_bot.db.base import Base
class History(Base):
__tablename__ = "history"
user_id: Mapped[str] = mapped_column(String(19), primary_key=True)
user_name: Mapped[str] = mapped_column(nullable=False)
cmd: Mapped[Literal["start", "end"]] = mapped_column(primary_key=True)
created_at: Mapped[dt.datetime] = mapped_column(primary_key=True)
db/__init__.pyでHistoryテーブルとBaseクラスを参照するようにしておきます。
ここでHisotryテーブルを参照するようになっていないとマイグレーションファイルの生成時にHistoryテーブルが作成されません。
from mokumoku_bot.db.base import Base
from mokumoku_bot.model.history import History
実装出来たら、env.pyを以下のように編集します。
from mokumoku_bot.db import Base
# ...中略...
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = Base.metadata
これでマイグレーションファイルを作成します。
uv run alembic revision --autogenerate -m "create initial table"
以下のようなマイグレーションファイルが生成されます。
興味深いのはcmdのリテラルがenumとして生成されている部分ですね。
ちゃんとリテラルも考慮して実装されているんですね。すごい。
"""create initial table
Revision ID: f9372abd0049
Revises:
Create Date: 2026-01-26 13:21:28.739946
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'f9372abd0049'
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('history',
sa.Column('user_id', sa.String(length=19), nullable=False),
sa.Column('user_name', sa.String(), nullable=False),
sa.Column('cmd', sa.Enum('start', 'end', native_enum=False), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('user_id', 'cmd', 'created_at')
)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('history')
# ### end Alembic commands ###
マイグレーションの適用
先ほどのマイグレーションファイルを適用しましょう。
❯ uv run alembic upgrade head
INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> f9372abd0049, create initial table
無事適用できたようです。
ダッシュボードを確認してみます。
マイグレーションの履歴テーブルと、先ほど作成した履歴テーブルが作成されていますね!
感想
Neonに関してはログインしてプロジェクト作ったら即使えたので、alembicの解説の方が長くなってしまいました。
それだけ手間が少なく使えるわけですから、すごいサービスだなぁと思います。