Drizzle ORM でマイグレーションが動かない・スキーマが反映されない
Drizzle ORM は TypeScript-first な軽量 ORM として Prisma の代替として急速に普及しています。しかし、マイグレーション周りの挙動が Prisma と大きく異なるため、初めて使う際にハマるポイントが多くあります。
Error: No config path provided
SSL connection is required
TypeError: Cannot read properties of undefined (reading 'findMany')
このような問題で詰まった方向けに、5つの主要な原因と対処法を解説します。
原因1:push と migrate の使い分けを間違えている
Drizzle ORM のマイグレーション方法は2種類あります。
| コマンド | 用途 | リスク |
|---|---|---|
drizzle-kit push |
スキーマを直接 DB に同期(開発用) | カラム削除でデータ消失の可能性あり |
drizzle-kit generate + migrate |
マイグレーションファイルを生成して適用(本番用) | 安全 |
push の落とし穴
push は「現在のスキーマ定義と DB を一致させる」コマンドです。カラムのリネームや削除が即時実行されるため、本番環境で使うとデータが消えます。
# NG: 本番環境で直接 push するのは危険
npx drizzle-kit push
# OK: ファイルを生成して内容を確認してから適用
npx drizzle-kit generate
npx drizzle-kit migrate
Prisma では prisma migrate dev がマイグレーションファイルの生成と適用を一括でやってくれますが、Drizzle では生成(generate)と適用(migrate)が別コマンドです。この違いを把握していないと本番での事故につながります。
開発環境では push、本番では migrate
# 開発環境(速い・手軽)
npx drizzle-kit push
# 本番環境(安全・履歴が残る)
npx drizzle-kit generate # drizzle/ フォルダにSQLファイルが生成される
npx drizzle-kit migrate # 未適用のマイグレーションを実行
原因2:drizzle.config.ts の schema パスが間違っている
drizzle-kit がスキーマを見つけられない場合、次のようなエラーが出ます。
No schema found. Please check your drizzle config file.
Error: ENOENT: no such file or directory, open './src/db/schema.ts'
drizzle.config.ts の schema フィールドが実際のファイルパスと一致しているか確認してください。
// drizzle.config.ts
import { defineConfig } from 'drizzle-kit'
export default defineConfig({
schema: './src/db/schema.ts', // ← 実際にファイルが存在するパス
out: './drizzle', // マイグレーションファイルの出力先
dialect: 'postgresql',
dbCredentials: {
url: ({}).DATABASE_URL!,
},
})
スキーマを複数ファイルに分割している場合は glob パターンが使えます。
// スキーマを複数ファイルに分割している場合
export default defineConfig({
schema: './src/db/schema/*.ts', // フォルダ内の全 .ts ファイルを対象に
// または配列で列挙
// schema: ['./src/db/schema/users.ts', './src/db/schema/posts.ts'],
})
よくあるミスとして、src/db/index.ts でスキーマを re-export しているのに config では schema/index.ts ではなく個別ファイルを指定すべきところを index.ts にしている、などがあります。
原因3:Neon / Vercel Postgres で接続文字列が間違っている(プールド vs 直接接続)
Neon や Vercel Postgres には プールド接続(pooled) と 直接接続(direct / unpooled) の2種類があります。
用途によって使い分けが必要です。
| 用途 | 接続の種類 |
|---|---|
| アプリ実行(Next.js など) | プールド接続(接続数を節約できる) |
drizzle-kit migrate の実行 |
直接接続(必須) |
| Edge Runtime(Vercel Edge / Cloudflare Workers) | neon-http アダプター |
プールド接続でマイグレーションを実行すると、以下のようなエラーになることがあります。
Error: prepared statement "drizzle_stmt_0" already exists
NeonDbError: prepared statement conflicts with existing prepared statement
Neon の場合、Vercel との連携時には環境変数が自動で設定されます。
# アプリ実行用(プールド)
DATABASE_URL="postgresql://user:pass@ep-xxx.us-east-1.aws.neon.tech/neondb?sslmode=require"
# マイグレーション用(直接接続)
POSTGRES_URL_NON_POOLING="postgresql://user:pass@ep-xxx.us-east-1.aws.neon.tech/neondb?sslmode=require"
drizzle.config.ts ではマイグレーション専用の直接接続 URL を使います。
// drizzle.config.ts(マイグレーション用)
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
// プールド接続の DATABASE_URL ではなく直接接続 URL を使う
url: ({}).POSTGRES_URL_NON_POOLING ?? ({}).DATABASE_URL!,
},
})
原因4:SSL 設定が足りない
ローカルでは動いていたのに Neon や Vercel Postgres に接続するとエラーになるケースです。
Error: SSL connection is required. Please make sure that you are connecting to PostgreSQL using SSL/TLS.
Neon などのホスティング型 Postgres では SSL が必須です。接続文字列に ?sslmode=require を追加するか、接続オプションで SSL を指定してください。
Edge Runtime(neon-http)の場合
// src/db/index.ts(Edge Runtime / Next.js App Router 対応)
import { neon } from '@neondatabase/serverless'
import { drizzle } from 'drizzle-orm/neon-http'
import * as schema from './schema'
const sql = neon(({}).DATABASE_URL!)
export const db = drizzle(sql, { schema })
Node.js(pg)の場合
// src/db/index.ts(通常の Node.js サーバー向け)
import { drizzle } from 'drizzle-orm/node-postgres'
import { Pool } from 'pg'
import * as schema from './schema'
const pool = new Pool({
connectionString: ({}).DATABASE_URL,
ssl: {
rejectUnauthorized: false, // 本番ホスティングでは通常 false
},
})
export const db = drizzle(pool, { schema })
接続文字列に直接 SSL パラメーターを含める方法もあります。
DATABASE_URL="postgresql://user:pass@host/db?sslmode=require&channel_binding=disable"
原因5:db.query.tableName が undefined になる(Relational Query API)
Drizzle ORM の Relational Query API(db.query.users.findMany() のような書き方)は、スキーマを正しく渡さないと動作しません。
TypeError: Cannot read properties of undefined (reading 'findMany')
TypeError: db.query is undefined
よくあるミスは、drizzle() 呼び出し時にスキーマを渡し忘れることです。
// NG: スキーマなしで初期化するとdb.queryが使えない
import { drizzle } from 'drizzle-orm/node-postgres'
const db = drizzle(client)
db.query.users.findMany() // TypeError: Cannot read properties of undefined
// OK: スキーマを第2引数で渡す
import { drizzle } from 'drizzle-orm/node-postgres'
import * as schema from './schema'
const db = drizzle(client, { schema })
// これで型安全に使えるようになる
const users = await db.query.users.findMany({
with: {
posts: true,
},
})
また、relations を定義していない場合も with が型エラーになります。スキーマファイルで relations を明示的に定義する必要があります。
// schema.ts
import { pgTable, serial, text, integer } from 'drizzle-orm/pg-core'
import { relations } from 'drizzle-orm'
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
})
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
userId: integer('user_id').references(() => users.id),
})
// relations を定義しないと with が使えない
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}))
export const postsRelations = relations(posts, ({ one }) => ({
user: one(users, {
fields: [posts.userId],
references: [users.id],
}),
}))
まとめ:Drizzle ORM マイグレーション トラブルシューティング チェックリスト
| 症状 | 確認すること |
|---|---|
| スキーマが DB に反映されない | drizzle.config.ts の schema パスが正しいか確認 |
push でデータが消えた |
本番では generate + migrate を使う |
マイグレーションが prepared statement エラーで失敗 |
直接接続 URL(non-pooling)を使う |
| SSL エラーで接続できない | ssl: { rejectUnauthorized: false } または ?sslmode=require を設定 |
db.query.xxx が undefined |
drizzle(client, { schema }) でスキーマを渡す |
with で型エラーが出る |
relations() を定義してスキーマに含める |
Drizzle ORM は Prisma よりもシンプルで高速ですが、マイグレーションの仕組みや接続方式の概念が異なるため、最初の設定でつまずきやすいです。特に 開発用の push と本番用の generate + migrate の使い分け、プールド接続と直接接続の使い分けの2点を押さえておくだけで、多くのトラブルを事前に防げます。
エラーメッセージが出たら、まずこの記事のチェックリストを上から順に確認してみてください。
