メインコンテンツへスキップ

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples on this page work on VillageSQL. Install Now →
Drizzle ORM は TypeScript のクエリビルダーで、スキーマファイルから SQL マイグレーションを生成し、独自の中間クエリ言語を介さずに型付きのクエリ API を提供します。このガイドでは、Drizzle と mysql2 ドライバーを通じて Next.js App Router アプリケーションを MySQL に接続し、スキーマ、マイグレーション、Server Component からの読み取り、Server Action からの書き込みを扱います。create-next-app --src-dir でスキャフォールドされた既存のアプリを前提としており、このコマンドがコードサンプルで使う @/* インポートエイリアスを設定します。

Drizzle と MySQL ドライバーのインストール

Drizzle が MySQL と通信するには mysql2 ドライバーが必要で、マイグレーションの生成と実行には drizzle-kit が必要です。
drizzle-kit は変化が速く、latest と並んで dist-tags の下にプレリリースタグ (beta、rc) を公開しています。インストールの前に npm view drizzle-orm dist-tags と npm view drizzle-kit dist-tags を確認し、latest がリリース候補ではなく実際に安定版リリースを指していることを確かめてください。 .env に接続文字列を設定します。

データベースユーザーの作成

アプリケーションを root として接続するのではなく、専用のデータベースと、それに限定されたユーザーを作成します。
Unix ソケット経由の接続は、どの IP ベースの権限付与が存在するかに関係なく、常に 'localhost' ホストと照合されます。'drizzle_app'@'127.0.0.1' としてのみ作成されたユーザーは、ソケット経由では認証できません。127.0.0.1 への TCP 接続は逆引き DNS で照合されるため、それが 'localhost' の権限付与も満たすかどうかは、固定された MySQL のルールではなく、サーバーが 127.0.0.1 をどう解決するかに依存します。アプリケーションが実際に使うホストに権限を付与し、127.0.0.1 と localhost が交換可能であることに頼らないでください。

スキーマの定義

Drizzle の MySQL スキーマは、drizzle-orm/mysql-core の関数を使って各列を記述します。
src/db/schema.ts
varchar には明示的な length が必要です。MySQL は PostgreSQL と異なり、最大長を列の型に組み込んだ形で VARCHAR を格納するため、Drizzle にはフォールバックするデフォルトがなく、最初から値が必要になります。

drizzle-kit の設定

drizzle-kit は、スキーマの場所とデータベースへの到達方法を知るために設定ファイルを読みます。
drizzle.config.ts
drizzle-kit は設定ファイルが実行される前に自身で .env を読み込むため、ここでは dotenv のインポートなしで process.env.DATABASE_URL がすでに設定されています。これは drizzle-kit に固有の動作です。素の Node や tsx のスクリプトにはそのようなローダーは組み込まれておらず、このガイドの後半の検証スクリプトがそれを示しています。 package.json にマイグレーション用のスクリプトを追加します。
package.json

マイグレーションの生成と実行

drizzle-kit generate はスキーマファイルを最後に生成されたマイグレーションと比較し、新しい SQL ファイルを書き出します。
drizzle-kit は設定とスキーマのパスを絶対パスで出力します。上の /path/to/your-app はプロジェクトディレクトリの代わりです。生成されるファイル名にはランダムな 2 語のサフィックスが含まれるため、あなたの環境では異なります。順序付けに意味があるのは数値のプレフィックス (0000) だけです。スキーマを変更せずに db:generate を再実行すると、2 つ目のファイルを書き出す代わりに No schema changes, nothing to migrate と報告します。マイグレーションそのものは素の SQL です。
drizzle/0000_premium_tiger_shark.sql
適用します。
drizzle-kit migrate は初回の実行時に __drizzle_migrations テーブルを作成し、どのマイグレーションファイルがすでに適用済みかを追跡します。両方のテーブルが存在することを確認します。

データベースクライアントの作成

mysql2 ドライバーはプールを通じて接続し、Drizzle がそのプールをラップします。
src/db/index.ts
mode: "default" オプションは mysql2 ドライバーに固有のもので、Drizzle の PostgreSQL や SQLite ドライバーには相当するものがありません。これを省略すると、暗黙のデフォルトにはならず、型エラーになります。

Server Component での投稿の読み取り

Server Component は、間に API ルートを挟まずに、レンダリング中に db.select() を直接呼び出せます。
src/app/page.tsx

Server Action での投稿の挿入

Server Action はサーバー上で実行され、フォームの action プロパティに直接渡せます。
src/app/actions.ts
revalidatePath("/") は、リクエストごとにレンダリングされるルートであっても必要です。これがないと、Server Action のレスポンスに、呼び出し元ページの更新されたコピーが含まれることは保証されません。

静的レンダリングの罠

npm run build はアプリケーションをコンパイルしますが、同時にルートごとに、ページをビルド時に一度だけプリレンダリングできるか、それともリクエストごとにレンダリングしなければならないかを決定します。同じルート内に他の動的 API (cookies() も headers() も searchParams もない) を持たずにデータベースを読む Server Component は、Next.js にリクエストごとのレンダリングを強制するものを何も与えないため、ビルド中にページを一度だけプリレンダリングし、その後はすべての訪問者に同じ HTML を配信します。 これは再現可能でした。page.tsx から export const dynamic = "force-dynamic"; を削除して再ビルドすると、ルートは static とマークされました。
そのビルドの後にデータベースへ直接挿入した行は、ビルド済みアプリケーションを npm run start で起動しても表示されませんでした。ページはビルド時に取り込んだ行数と内容を配信し続けました。page.tsx に export const dynamic = "force-dynamic"; を追加して再ビルドすると、ルートは dynamic に変わりました。
この変更の後、ビルド後に挿入した行は、再ビルドなしで次のリクエストですぐに表示されました。データベースから読み取り、ビルド後に行われた書き込みを反映する必要があるページには、export const dynamic = "force-dynamic"; か、リクエストごとのレンダリングを強制するという同じ効果を持つ別の Next.js API (cookies() の読み取りなど) が必要です。

ORM とは独立して書き込みパスを検証する

Drizzle を通じて挿入し、Drizzle 自身の戻り値だけを確認するスクリプトでは、行がデータベースに到達したことは証明できません。証明できるのは、ドライバーが例外を投げずに戻ったことだけです。別のクエリで検証してください。
src/db/verify-insert.ts
TypeScript を直接実行する tsx で実行します。
スクリプト先頭の import "dotenv/config" はここでは必須です。Next.js はアプリケーションのために .env を自動的に読み込みますが、tsx で実行する独立したスクリプトはフレームワークの配線を持たない素の Node プロセスであり、Node 自身は .env ファイルを読みません。 次に、アプリケーションを一切経由しないクエリで行の存在を確認します。

よくある質問

これは MySQL と同じように VillageSQL でも動作しますか?

はい。Drizzle の MySQL 方言と mysql2 ドライバーは標準の MySQL ワイヤプロトコルで通信し、VillageSQL は同じプロトコルとクライアントツールを持つ MySQL のドロップイン代替です。上のスキーマ、マイグレーション、クエリのコードには、MySQL のバージョンに固有のものは何もありません。

drizzle-kit generate と migrate を使うべきですか、それとも drizzle-kit push ですか?

generate と migrate の組み合わせは、データベースに対して実行する前にレビューしてコミットできる SQL マイグレーションファイルを書き出します。このガイドが使うのはこちらです。drizzle-kit push はスキーマファイルを稼働中のデータベースと比較し、マイグレーションファイルなしで差分を直接適用します。push は初期のプロトタイピングでは速いですが、何がいつ変わったかの記録を残さないため、他の開発者や環境がスキーマに依存するようになった後では、マイグレーションファイルの代わりにはなりません。

なぜマイグレーションフォルダーに __drizzle_migrations というテーブルがあるのですか?

drizzle-kit migrate は初回実行時にこのテーブルを作成し、どのマイグレーションファイルをすでに適用したかをハッシュで記録します。これによって migrate は、すでに実行済みのマイグレーションを二重に適用せずにスキップできます。

Server Action の代わりに Server Component から直接 db.insert() を呼べますか?

Server Component はレンダリング中にしか実行されません。ユーザーの操作に応じて書き込みを起動するブラウザからのリクエストは存在しません。書き込みパスには、Server Action、Route Handler、または API ルートを呼び出すクライアント側のコードが必要です。

トラブルシューティング

関連項目