空雲 Blog

Eye catch
Drizzle ORM から PostgreSQL を使用する際に、環境変数からschemaを切り替える

publication: 2026/01/19
update:2026/01/19

はじめに🔗


PostgreSQL には Schema(スキーマ) という概念があり、1 つのデータベース内に「名前空間」を作ってテーブルを管理できます。デフォルトでは public スキーマが使われますが、これを切り替えることで、たとえば「テナントごとにスキーマを分ける」「テスト実行時だけ隔離されたスキーマを使う」といった運用が可能になります。


Prisma ユーザーにはおなじみですが、PostgreSQL の接続 URL に ?schema=my_schema というパラメータを付けるだけで、接続先スキーマを切り替えられる機能があります。
しかし、これはあくまで Prisma が独自に実装している機能(糖衣構文)であり、PostgreSQL のドライバ標準の仕様ではありません。そのため、他の ORM やツールでは同様に記述しても無視されてしまいます。


Drizzle ORM も例外ではなく、通常の方法(pgSchema でのハードコーディング)では動的なスキーマ切り替えが困難です。本記事では、Drizzle ORM において環境変数(接続 URL)から動的にスキーマを切り替える実装方法を紹介します。


サンプルプロジェクト🔗


本記事で解説するコードの完成形は、以下のリポジトリで公開しています。


https://github.com/SoraKumo001/drizzle-pg-schema


使用技術🔗


  • Drizzle ORM / Drizzle Kit: ORM およびマイグレーションツール
  • PostgreSQL: データベース
  • Docker: 実行環境




課題:なぜ標準機能だけでは足りないのか🔗


Drizzle ORM で特定のスキーマを利用する場合、通常はスキーマ定義ファイル内で pgSchema("my_schema") を使って固定的に記述する必要があります。しかし、これでは開発・テスト・本番でスキーマ名を柔軟に変えたいニーズに対応できません。


PostgreSQL 自体には search_path という設定があり、これをセッション接続時に指定することでデフォルトのスキーマを変更できます。今回はこの search_path を活用しつつ、Drizzle Kit の CLI コマンド(migratepush)では対応しきれない部分をカスタムスクリプトで補うことで解決します。




実装のポイント🔗


実装の核となるのは以下の 3 点です。


  1. drizzle.config.ts: 環境変数からスキーマ名を読み取る。
  2. App 接続設定: search_path オプションを付与して接続する。
  3. カスタムスクリプト: マイグレーションやシード実行時にスキーマを明示的に指定する。


1. 設定ファイル (drizzle.config.ts)🔗


まず、DATABASE_URL から ?schema=xxx パラメータを解析し、Drizzle Kit に伝える設定を行います。


1import { defineConfig } from "drizzle-kit";
2import "dotenv/config";
3
4const connectionString = process.env.DATABASE_URL!;
5const url = new URL(connectionString);
6// クエリパラメータから schema を取得。なければ public
7const searchPath = url.searchParams.get("schema") ?? "public";
8
9export default defineConfig({
10 dialect: "postgresql",
11 schema: "./src/db/schema.ts",
12 out: "./drizzle",
13 dbCredentials: {
14 url: connectionString,
15 },
16 migrations: {
17 // マイグレーション管理テーブル(__drizzle_migrations)の作成先を指定
18 schema: searchPath,
19 },
20});
21


2. アプリケーションコードでの接続🔗


アプリケーションから DB に接続する際、PostgreSQL ドライバのオプションとして search_path を渡します。これにより、発行される SQL にスキーマ名が明記されていなくても、自動的に指定したスキーマがターゲットになります。


1import "dotenv/config";
2import { drizzle } from "drizzle-orm/node-postgres";
3import { relations } from "./db/relations.js";
4
5const connectionString = process.env.DATABASE_URL;
6if (!connectionString) throw new Error("DATABASE_URL is not set");
7
8const url = new URL(connectionString);
9const searchPath = url.searchParams.get("schema") ?? "public";
10
11const db = drizzle({
12 connection: {
13 connectionString,
14 // セッションレベルで search_path を設定
15 // これにより、以降のクエリはこのスキーマに対して実行される
16 options: `--search_path=${searchPath}`,
17 },
18 relations,
19});
20
21// 使用例
22const main = async () => {
23 const result = await db.query.posts.findMany({
24 with: { author: true, categories: true }
25 });
26 console.log(result);
27 db.$client.end();
28};
29
30main();
31




運用ツールの自作(Migrate, Seed, Reset)🔗


Drizzle Kit の CLI (drizzle-kit migrate 等)は便利ですが、動的なスキーマ切り替えや、環境ごとのクリーンアップを行うには柔軟性が足りない場合があります。そこで、Node.js スクリプトとして各コマンドを実装します。


これらのスクリプトは tools/ ディレクトリに配置し、drizzle.config.ts の設定を読み込んで動作させます。


A. マイグレーション実行 (tools/migrate.ts)🔗


CLI の代わりに drizzle-ormmigrate 関数を使用します。ここで重要なのが migrationsSchema オプションです。これを指定することで、マイグレーション履歴の管理テーブルも指定スキーマ内に作成されます。


1import config from "../drizzle.config";
2import { drizzle } from "drizzle-orm/node-postgres";
3import { migrate } from "drizzle-orm/node-postgres/migrator";
4
5const main = async () => {
6 // ... (接続チェック省略) ...
7
8 const searchPath = config.migrations?.schema ?? "public";
9
10 const db = drizzle({
11 connection: {
12 connectionString: config.dbCredentials.url,
13 options: `--search_path=${searchPath}`,
14 },
15 });
16
17 if (!config.out) throw new Error("out is not set");
18
19 // スキーマを指定してマイグレーションを実行
20 await migrate(db, {
21 migrationsFolder: config.out,
22 migrationsSchema: searchPath,
23 });
24
25 await db.$client.end();
26};
27
28main();
29


B. データベースリセット (tools/reset.ts)🔗


開発中、DB を完全にクリーンな状態に戻したい場合があります。drizzle-kit にはこの機能が不足しているため、指定されたスキーマごと削除(DROP SCHEMA ... CASCADE)するスクリプトを用意します。


1// ... (imports) ...
2
3const main = async () => {
4 // ... (config読み込み) ...
5 const searchPath = config.migrations?.schema ?? "public";
6
7 const db = drizzle({
8 connection: {
9 connectionString: config.dbCredentials.url,
10 options: `--search_path=${searchPath}`,
11 },
12 });
13
14 // スキーマごと削除してカスケード
15 await db.execute(`DROP SCHEMA IF EXISTS "${searchPath}" CASCADE`);
16
17 // スキーマを再作成(必要に応じて)
18 await db.execute(`CREATE SCHEMA IF NOT EXISTS "${searchPath}"`);
19
20 await db.$client.end();
21};
22
23main();
24


C. シーディング (tools/seed.ts)🔗


drizzle-seed を使用しますが、ここにも注意点があります。標準の reset 機能を使うと、スキーマ名が正しく扱われず public などを誤って参照してしまう挙動(バグまたは仕様)が見られました。
そのため、手動で TRUNCATE を行ってからデータを投入します。


1import config from "../drizzle.config";
2import { drizzle } from "drizzle-orm/node-postgres";
3import { seed } from "drizzle-seed";
4import { sql, isTable } from "drizzle-orm";
5import { getTableConfig, type PgTable } from "drizzle-orm/pg-core";
6import * as schema from "../src/db/schema"; // スキーマ定義をインポート
7
8const main = async () => {
9 // ... (config読み込み) ...
10 const searchPath = config.migrations?.schema ?? "public";
11
12 const db = drizzle({
13 connection: {
14 connectionString: config.dbCredentials.url,
15 options: `--search_path=${searchPath}`,
16 },
17 });
18
19 await db.transaction(async (tx) => {
20 // 全テーブルを動的に取得して TRUNCATE する
21 const tables = Object.values(schema)
22 .filter((t) => isTable(t))
23 .map((t) => `"${getTableConfig(t as PgTable).name}"`)
24 .join(", ");
25
26 if (tables.length > 0) {
27 await tx.execute(sql.raw(`TRUNCATE TABLE ${tables} CASCADE;`));
28 }
29
30 // データの投入
31 await seed(tx, schema);
32 });
33
34 await db.$client.end();
35};
36
37main();
38




実行方法🔗


package.json に以下のようにスクリプトを定義しておくとスムーズに運用できます。


1{
2 "scripts": {
3 "dev": "tsx src/index.ts",
4 "docker": "docker compose up -d",
5 "generate": "drizzle-kit generate",
6 "migrate": "tsx tools/migrate.ts",
7 "seed": "tsx tools/seed.ts",
8 "reset": "tsx tools/reset.ts"
9 }
10}


ワークフロー🔗


  1. .env の設定: URL にターゲットスキーマを指定します。


1DATABASE_URL=postgres://user:pass@localhost:5432/db?schema=test_schema
2


  1. DB 起動: pnpm run docker
  2. マイグレーション生成: pnpm run generate (スキーマ変更時)
  3. 適用 & データ投入:


1pnpm run migrate # 指定したスキーマにテーブル作成
2pnpm run seed # 指定したスキーマにデータ投入
3


  1. アプリ実行: pnpm run dev


これで、test_schema 配下にデータが作られ、アプリもそこを参照して動くようになります。環境変数を書き換えるだけで、コードを変更することなく接続先スキーマをスイッチ可能です。




まとめ🔗


Drizzle ORM で動的なスキーマ切り替えを実現するには、標準の pgSchema 定義ではなく、以下の組み合わせが有効です。


  1. PostgreSQL 標準の search_path を接続オプションで利用する。
  2. drizzle-kit の CLI に頼らず、migrateseed をプログラム(スクリプト)から実行し、その際にスキーマ情報を注入する。


このアプローチにより、Prisma のような手軽さで、開発・テスト・本番環境のスキーマ分離を Drizzle でも実現できます。ぜひ試してみてください。