---
title: "SupabaseにPrismaとDrizzleをつなぐ：接続文字列とマイグレーション二重管理の設計"
url: "https://www.issoh.co.jp/tech/details/17072/"
published: 2026-08-28
updated: 2026-09-06
categories: ["データベース"]
publisher: "株式会社一創"
---

# SupabaseにPrismaとDrizzleをつなぐ：接続文字列とマイグレーション二重管理の設計

Supabase の接続文字列を `DATABASE_URL` に貼って Prisma や Drizzle をつなぐ。ここまでは数分です。詰まるのはその先で、開発機からは通るのに本番のコンテナからだけ到達しない、マイグレーションを流したら履歴が二重になった、行レベルセキュリティを書いたのに ORM 経由だと全行返ってくる、と三段構えで足が止まる。原因はコードの書き方ではなく、Supabase 側と ORM 側の前提が噛み合っていない点にあります。

この記事は、まず Prisma を入れて最初のクエリが返るまでを手を動かす順に並べ、そのうえで接続経路の選び分け、スキーマの正の置き場所、ポリシーを効かせる手当てを扱います。数値と版は 2026年9月6日に公式ドキュメントと npm レジストリを実測した結果です。掲載したコマンドと設定はそのまま写して動かせる形にしてあります。

## まとめ：接続先とスキーマの正を先に決める3つの判断

決めるべきは3点です。第一に接続経路。常駐するバックエンドから ORM をつなぐなら直結または専用プーラー、サーバーレスやエッジならトランザクションプーラーへ寄せてください。

第二にスキーマの正。ORM のマイグレーション機構と supabase CLI は独立した履歴テーブルを持ちます。両方に書かせると片方だけ進んだ状態が生まれて巻き戻せない。片方を正と決め、もう片方は読み取り専用に落としてください。

第三に認可の位置です。既定の接続ユーザーは管理権限を持つロールで、テーブルの所有者にもなるため、そのままではポリシーを通りません。「書いたのに効いていない」という形で表面化します。

加えて、2026年9月時点では版そのものが動いています。`prisma` の `latest` タグは 8.0 系のリリース候補を指す一方、`@prisma/client` の `latest` は 7.10.0 で止まっている。素直に入れると CLI とクライアントで世代が割れます。導入コマンドの時点で版を明示してください。

逆に、ORM を入れないほうが速い構成もある。単一テーブルの読み書きが中心で認可をポリシーに寄せきれるなら、[Supabaseの自動生成APIとRPCの記事](https://www.issoh.co.jp/tech/details/17070/)で扱った PostgREST 経由で足ります。分かれ目は、守りたい不変条件が複数テーブルにまたがるかどうかの一点。製品としての全体像は[SupabaseとFirebaseの違いを比較した記事](https://www.issoh.co.jp/column/details/3002/)が先です。

## Prismaを入れてSupabaseにつなぐ：初期化からクエリ実行までの手順

先に手を動かす順を通します。ここで扱うのは、パッケージの導入、スキーマの記述、マイグレーションの適用、クライアント生成、最初のクエリまで。Prisma を初めて触る場合はこの節だけで動くところまで到達します。Drizzle で同じことをやる場合の対応も末尾に置きました。

### パッケージを入れる：prisma本体とドライバアダプタの版を指定する

Supabase は PostgreSQL なので、必要なのは Prisma CLI・Prisma Client・PostgreSQL 用ドライバアダプタの3つです。版を書かずに入れると後述の理由で CLI だけリリース候補になるため、この段階で完全指定にします。

```
# 2026年9月6日時点の安定版で3点を揃える
npm i @prisma/client@7.10.0 @prisma/adapter-pg@7.10.0 pg
npm i -D prisma@7.10.0

# prisma/schema.prisma と .env の雛形を作る
npx prisma init

# 実際に入った版を確認する
npm ls prisma @prisma/client @prisma/adapter-pg
```

環境変数は2本用意します。実行時にクライアントが使うプール経由の `DATABASE_URL` と、CLI が使う直結の `DIRECT_URL` です。Prisma の Supabase 向けガイドも[この2本立てを前提に書かれています](https://www.prisma.io/docs/orm/overview/databases/supabase)。値はダッシュボードの Connect 画面に表示されたものを写し、地域の接頭辞やポート番号を推測で埋めないでください。

### スキーマを書いて型を生成する：モデル定義からクライアント生成まで

スキーマは `prisma/schema.prisma` に書きます。v7 以降は生成先を明示する書き方が既定で、生成物の置き場所を `generator` ブロックの `output` で指定します。

```
// prisma/schema.prisma
generator client {
  provider = "prisma-client"
  output   = "../src/generated/prisma"
}

datasource db {
  provider = "postgresql"
}

model Post {
  id        String   @id @default(uuid()) @db.Uuid
  authorId  String   @db.Uuid
  title     String
  body      String?
  createdAt DateTime @default(now())

  @@index([authorId])
}
```

書けたらマイグレーションを作って流し、型を生成します。開発中は差分の生成と適用をまとめて行い、本番へは生成済みのマイグレーションだけを適用する分け方が基本線です。

```
# 差分から SQL を作り、開発用データベースへ適用する
npx prisma migrate dev --name init

# TypeScript の型とクライアントを生成する
npx prisma generate

# 本番・検証環境へは適用だけを行う
npx prisma migrate deploy
```

`migrate dev` は直結の接続を要求します。次章のとおりトランザクションプーラー（6543）では流せないため、`DIRECT_URL` 側が直結かセッションモードを指しているか確認してから叩いてください。

### クエリを実行して確認する：selectとinsertの最小コード

クライアントはアダプタ経由で作ります。生成した型が効くので、列名の打ち間違いはコンパイル時に落ちます。

```
// src/db.ts
import { PrismaPg } from '@prisma/adapter-pg'
import { PrismaClient } from './generated/prisma/client'

const adapter = new PrismaPg({
  connectionString: process.env.DATABASE_URL,
})
export const prisma = new PrismaClient({ adapter })
```

```
// src/usecase.ts
import { prisma } from './db'

const created = await prisma.post.create({
  data: { authorId: userId, title: '最初の記事' },
})

const rows = await prisma.post.findMany({
  where: { authorId: userId },
  select: { id: true, title: true, createdAt: true },
  orderBy: { createdAt: 'desc' },
  take: 20,
})
```

作成・取得・更新・削除の一覧は[Prisma のクエリリファレンス](https://www.prisma.io/docs/orm/prisma-client/queries/crud)にまとまっています。ここまでで読み書きは通りますが、この状態では行レベルセキュリティが評価されていません。理由と手当ては後段の章で扱います。

### Drizzle側の最小手順：スキーマ定義から適用までの全体の流れ

Drizzle は生成物を SQL ファイルとして吐き、それを適用する二段構えです。ドライバは自分で選びます。Drizzle 公式の Supabase 向けガイドは[リリース候補系の版を指定するインストール手順](https://orm.drizzle.team/docs/connect-supabase)を示しています。

```
# 公式ガイドが指定している形（rc タグ）
npm i drizzle-orm@rc postgres
npm i -D drizzle-kit@rc

# スキーマから SQL を生成し、適用する
npx drizzle-kit generate
npx drizzle-kit migrate
```

スキーマ定義の書き方そのものは[Drizzle ORMとはを解説した記事](https://www.issoh.co.jp/tech/details/7939/)に譲ります。ここで押さえるのは、Prisma が「差分検出と適用を一体で持つ」のに対し、Drizzle は「生成と適用が分かれる」という構造の違いです。この差が後段のマイグレーション二重管理の設計に効いてきます。

## 接続文字列の使い分け：直結・セッション・トランザクションの3経路

接続先は1つではありません。ORM 側の設定を書く前に、どの経路をどこで使うかを決めてください。以下は[Supabase 公式の接続ガイド](https://supabase.com/docs/guides/database/connecting-to-postgres)が掲げる4通りの整理です。

| 経路               | ポート  | IP     | 向く用途         |
| ---------------- | ---- | ------ | ------------ |
| 直結               | 5432 | IPv6中心 | 常駐処理・移行・ダンプ  |
| 共有プーラー（セッション）    | 5432 | IPv4のみ | IPv4網からの常駐接続 |
| 共有プーラー（トランザクション） | 6543 | IPv4のみ | サーバーレスとエッジ   |
| 専用プーラー           | 6543 | IPv6中心 | 有料プランの高負荷用   |

### 直結接続はIPv6が前提：つながらないときに最初に見る設定値

直結のホストは `db.<project-ref>.supabase.co`、ポートは 5432 です。公式ドキュメントはこの経路を IPv6、または IPv4 アドオンを有効にした場合の IPv4 と説明しています。IPv6 が出ていない実行環境からは、文字列が正しくても到達しません。

これが「開発機からは通るのに CI からだけ落ちる」症状の正体です。パスワードを貼り替える前に、実行環境から IPv6 が引けるかを確認してください。

```
# 直結ホストの AAAA と A を引いて経路の有無を確かめる
nslookup -type=AAAA db.<project-ref>.supabase.co
nslookup -type=A    db.<project-ref>.supabase.co

# TCP で 5432 に届くかだけを切り分ける
nc -vz db.<project-ref>.supabase.co 5432
```

回避策は2つ。IPv4 アドオンを有効にするか、共有プーラーのセッションモードへ切り替えるか。後者は追加費用なしで全プランから使えます。前者を選ぶ場合、公式ドキュメントはアドオンがデュアルスタックではないと明記しています。有効にするとプロジェクトの AAAA レコードが A レコードへ入れ替わり、エンドポイントは IPv4 からのみ到達可能になる。IPv6 前提で組んだ経路が残っていると、そちら側が落ちます。

プーラー経由ではユーザー名が `postgres.<project-ref>` の形になり、ホストも `aws-<region>.pooler.supabase.com` 側へ変わる。地域の接頭辞はプロジェクトの作成時期で違うため、推測せずダッシュボードの表示を写してください。

### セッションモードとトランザクションモードの違いと6543の制約

セッションモード（5432）は接続をクライアント単位で貸し出すため、通常の Postgres 接続とほぼ同じ振る舞いです。トランザクションモード（6543）はトランザクション単位で貸し出し、同じ接続が別のリクエストへ渡る前提の設計です。

効いてくるのは prepared statement の扱い。公式ドキュメントはトランザクションモードが prepared statement を扱えないと明記し、接続ライブラリ側で無効化するよう案内しています。準備済みの文はセッションに紐づくため、接続を使い回す前提と噛み合わない。数回は成功し、負荷が上がったところで該当の文が見つからないというエラーが散発します。

マイグレーションも同じ理由で 6543 には向きません。直結かセッションモードを選んでください。運用側の手順は[Supabase CLIとローカル開発環境の記事](https://www.issoh.co.jp/tech/details/17063/)で整理しています。

### Prisma v7以降の接続設定：prisma.config.tsとアダプタに分ける

従来は `schema.prisma` の `datasource` ブロックに `url` と `directUrl` を並べ、実行時はプーラー、CLI は直結と書き分けていました。v7 ではこのブロックの `url`・`directUrl`・`shadowDatabaseUrl` が非推奨になり、CLI が使う接続は `prisma.config.ts` 側へ移っています。[設定リファレンス](https://www.prisma.io/docs/orm/reference/prisma-config-reference)には `datasource.directUrl` が v7 で削除済みで、`url` に置き換わったと記載されています。

さらに v7 ではドライバアダプタが必須になり、PostgreSQL では `@prisma/adapter-pg` を通してクライアントを作ります。Rust 製エンジンは廃止され、接続文字列はアダプタの引数として渡す形。2本の文字列は別ファイルに分かれて置かれます。設定ファイルは公式が示す `defineConfig` と `env` を使う形が正です。

```
// prisma.config.ts … CLI（migrate や introspect）が使う直結側
import "dotenv/config";
import { defineConfig, env } from "prisma/config";

export default defineConfig({
  schema: "prisma/schema.prisma",
  datasource: {
    url: env("DIRECT_URL"),
  },
});
```

```
# .env … 実行時はプーラー、CLI は直結
DATABASE_URL="postgres://postgres.<project-ref>:<password>@aws-0-<region>.pooler.supabase.com:6543/postgres?pgbouncer=true"
DIRECT_URL="postgresql://postgres:<password>@db.<project-ref>.supabase.co:5432/postgres"
```

Prisma のドキュメントは Supabase について、6543 を使う場合に `?pgbouncer=true` を付ける記述を残しています。ただし元はエンジン側の挙動を切り替えるパラメータ。アダプタ経由の構成では接続ライブラリ側の設定と併せて確認してください。版ごとの差分は[Prismaバージョンの変更点をまとめた記事](https://www.issoh.co.jp/tech/details/9996/)にあります。

### Drizzleでプーラーを使うときのprepare設定とドライバ選び

Drizzle はドライバを自分で選ぶ設計なので、モードに合わせた設定はドライバ側に書きます。`postgres-js` で 6543 へつなぐなら prepared statement を切る指定が必要。`node-postgres` なら、名前付きの準備済み文を作らない限り素通りします。公式ガイドも同じ理由で `prepare: false` を添えるよう書いています。

```
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'

// トランザクションプーラー（6543）へつなぐ場合
const client = postgres(process.env.DATABASE_URL!, {
  prepare: false,
})
export const db = drizzle({ client })
```

常駐するバックエンドから直結する場合は `prepare` を切る必要がありません。同じコードベースで両方の経路を使うなら、環境変数でポートを見て設定を分けるより、接続を作るモジュールを2本に分けたほうが事故が減ります。クライアントの置き場所は[SupabaseとNext.jsを連携させる記事](https://www.issoh.co.jp/tech/details/17065/)を参照してください。

## マイグレーションの二重管理：3つの台帳を1本へ寄せる設計判断

ここが実務でいちばん揉めます。supabase CLI と ORM に同じ役割の機構があり、両方を素直に使うと履歴が並走します。

### 台帳は3つ並ぶ：ORM側とsupabase CLI側で別に記録される

記録先はそれぞれ独立しています。同じデータベースの中に、互いを知らない台帳が並ぶ構造です。

| 台帳                     | 置き場所                 | 書き手                 |
| ---------------------- | -------------------- | ------------------- |
| `_prisma_migrations`   | publicスキーマ           | prisma migrate      |
| `__drizzle_migrations` | drizzleスキーマ          | drizzle-kit migrate |
| `schema_migrations`    | supabase\_migrations | supabase db push    |

Drizzle の既定値は公式ドキュメントに `{ table: "__drizzle_migrations", schema: "drizzle" }` と書かれています。Supabase 側は `supabase_migrations.schema_migrations` で、[公式のマイグレーション運用ガイド](https://supabase.com/docs/guides/deployment/database-migrations)が示すとおりファイルは `supabase/migrations/<timestamp>_<name>.sql` というフラットな配置になります。

ORM で列を追加して ORM 側の台帳に記録すると、CLI から見れば「手で変えた差分」に見えます。次に差分の生成を回せば、その変更を打ち消す SQL や重複して作り直す SQL が出てくる。逆に CLI で流した変更は ORM のスナップショットに残らないため、ORM 側の次回生成でも同じ列を作ろうとする。正が2つある状態そのものが原因です。

どの台帳が実際に使われているかは SQL で確認できます。移譲や引き継ぎの直後は、まずここを見てから設計を決めてください。

```
-- 3つの台帳のうち実在するものを一覧する
select table_schema, table_name
from information_schema.tables
where (table_schema, table_name) in (
  ('public', '_prisma_migrations'),
  ('drizzle', '__drizzle_migrations'),
  ('supabase_migrations', 'schema_migrations')
);
```

### ORM側を正にする構成：出力先をsupabaseのフォルダへ向ける

主にコードでスキーマを書くなら、ORM 側を正にするのが素直です。Drizzle では `out` を supabase のマイグレーションフォルダへ向け、生成物を CLI にも読ませます。生成は ORM、適用と本番反映は CLI という分担になり、台帳は CLI 側の1本に寄る形。設定キーの一覧は[Drizzle の設定ファイル・リファレンス](https://orm.drizzle.team/docs/drizzle-config-file)にあります。

```
// drizzle.config.ts
import { defineConfig } from 'drizzle-kit'

export default defineConfig({
  dialect: 'postgresql',
  schema: './src/db/schema.ts',
  out: './supabase/migrations',
  schemaFilter: ['public'],
  entities: {
    roles: { provider: 'supabase' },
  },
})
```

CLI 側はタイムスタンプと名前をつないだフラットな配置を前提にしているため、生成物のファイル名や階層が揃うかは入れた版で確認してください。Prisma で同じ形を取るなら差分から SQL を吐いて supabase のフォルダへ置き、適用は CLI に任せる。開発用のマイグレーションコマンドを本番へ向けない線引きが要点です。

### CLI側を正にする構成：ORMは型生成と読み取りだけに使う形

もう一方は、スキーマを SQL で書いて CLI で流し、ORM はイントロスペクションで追随させるやり方です。データベース管理者が別にいる体制、既存の Postgres を引き継いだ案件、拡張や関数を多用する設計ではこちらが安定します。

Prisma なら `prisma db pull` で取り込み、以降マイグレーション機構は使わない。Drizzle なら `drizzle-kit pull` で TypeScript のスキーマを生成します。ORM 側の台帳は作られないため、履歴は `supabase_migrations.schema_migrations` の1本だけ。取り込み後に手でスキーマへ変更を入れない運用ルールが守れるかが成否を決めます。履歴がずれたときの直し方は[Supabase CLIの記事](https://www.issoh.co.jp/tech/details/17063/)を手元に置いてください。

### authやstorageのスキーマを管理対象から外す設定の書き方

Supabase のデータベースには、製品が管理するスキーマが最初から入っています。`auth`、`storage`、`realtime` などです。ORM のイントロスペクションはこれらも見つけるため、除外しないと内部テーブルを削除する危険な差分が生成されます。

Drizzle の設定には除外の口があります。`schemaFilter` で対象スキーマを絞り、`tablesFilter` でテーブル名を、`extensionsFilters` で拡張が作るテーブルを外す。ロールは `entities.roles` に `provider` を指定でき、公式ドキュメントは Supabase が定義するロールを除外する用途として説明しています。

```
entities: {
  roles: {
    provider: 'supabase',
    exclude: ['new_supabase_role'],
  },
},
extensionsFilters: ['postgis'],
```

Prisma では対象スキーマの指定を `public` に絞るのが基本線。加えて生成された SQL を必ず目で見てください。内部テーブルへ削除文が書かれた状態で本番へ流すと、認証が丸ごと止まります。

## ORM経由でRLSを効かせる：所有者は素通りするという前提の扱い

行レベルセキュリティを書いて自動生成 API では期待どおり絞られている。ところが ORM 経由の同じクエリでは全行返ってくる。差の理由は、接続してくるロールが違うことです。

### ポリシーが効かない理由：所有者は評価を通らないという既定の挙動

PostgreSQL の既定では、テーブルの所有者は行レベルセキュリティの評価を通りません。[公式ドキュメント](https://www.postgresql.org/docs/current/ddl-rowsecurity.html)は、テーブル所有者がポリシーの対象外であること、所有者にも適用したい場合は強制の指定が要ることを明記しています。

Supabase の接続文字列に入っているユーザーは `postgres` で、[公式のロール解説](https://supabase.com/docs/guides/database/postgres/roles)はこれを管理権限を持つ既定のロールと説明しています。テーブルを作れば所有者もこのロールです。つまり ORM は所有者としてつないでいるので、ポリシーは評価されません。自動生成 API 側は `anon` や `authenticated` へ切り替わってから評価されるため、同じテーブルでも結果が変わる。なお `service_role` は、PostgREST が行レベルセキュリティをバイパスするために使うロールだと公式に明記されています。

手当ての方向は2つ。テーブル側で強制を明示するか、権限を絞った専用ロールを作ってそちらでつなぐか。前者だけでは所有者に一律で適用されて管理系の処理が止まるため、実務では後者と組み合わせます。鍵とロールの対応は[SupabaseのAPIキー管理の記事](https://www.issoh.co.jp/tech/details/17068/)、ポリシー本体の書き方は[Supabase RLSの設定手順の記事](https://www.issoh.co.jp/tech/details/17056/)にあります。

### 専用ロールを作りトランザクション内でロールとクレームを差し替える

実装の形はどちらの ORM でも同じです。1つのトランザクションを開き、その中でロールを `authenticated` などへ切り替え、JWT のクレームをセッション変数へ流し込んでから本来のクエリを実行します。適用範囲をトランザクション内に限る指定を添えれば、終了時に元へ戻ります。

```
begin;
select set_config('request.jwt.claims', '{"sub":"...","role":"authenticated"}', true);
set local role authenticated;
-- ここで発行するクエリはポリシーの評価を受ける
select id, title from posts;
commit;
```

これでポリシー側が参照する現在のユーザー識別子がクレームから解決されます。切り替えを忘れた経路が1本でも残ると、そこだけ全行見える状態に。生のクライアントをアプリコードから読み込めない構造にし、管理用と利用者用の接続を別モジュールへ分けてください。

### Drizzleの公式ヘルパー：pgPolicyとsupabase向けロール定義

Drizzle にはこの用途のヘルパーがあります。ポリシーは `pgPolicy` で宣言し、`as`・`to`・`for`・`using`・`withCheck` を渡す形。既存テーブルへ後から紐づけるときは `.link()` を使います。

Supabase 向けには専用のエントリポイントがあり、[公式の行レベルセキュリティ解説](https://orm.drizzle.team/docs/rls)には `anonRole`・`authenticatedRole`・`serviceRole`・`postgresRole`・`supabaseAuthAdminRole` というロール定義、`authUsers`・`realtimeMessages` というテーブル定義、`authUid`・`realtimeTopic` というヘルパーが挙げられています。いずれも既存扱いで、マイグレーションの生成対象から外れます。`authUid` の実体は `(select auth.uid())` を包んだ SQL 片です。

```
import { pgTable, pgPolicy, uuid, text } from 'drizzle-orm/pg-core'
import { sql } from 'drizzle-orm'
import { authenticatedRole, authUid } from 'drizzle-orm/supabase'

export const posts = pgTable('posts', {
  id: uuid().primaryKey().defaultRandom(),
  authorId: uuid().notNull(),
  body: text(),
}, (t) => [
  pgPolicy('posts_owner', {
    for: 'all',
    to: authenticatedRole,
    using: sql`${authUid} = ${t.authorId}`,
    withCheck: sql`${authUid} = ${t.authorId}`,
  }),
])
```

ポリシーをスキーマ定義と同じファイルに書けるのが利点です。追加や変更もマイグレーションとして生成され、レビューの対象に載ります。

### Prismaでの実装：クライアント拡張でトランザクションを包む

Prisma にはポリシーを宣言する機構がないため、ポリシー本体は SQL 側に置きます。ORM 側で用意するのは、ロールとクレームを差し替えたトランザクションでクエリを包む薄い層。クライアント拡張として書き、アプリからは拡張済みのクライアントだけを使わせます。

```
export function forUser(prisma: PrismaClient, claims: string) {
  return prisma.$extends({
    query: {
      $allModels: {
        async $allOperations({ args, query }) {
          const [, result] = await prisma.$transaction([
            prisma.$executeRaw`select set_config('request.jwt.claims', ${claims}, true)`,
            query(args),
          ])
          return result
        },
      },
    },
  })
}
```

ロールの切り替えは識別子なのでパラメータで渡せません。許可するロール名を配列で持ち、一致確認を通したものだけを文へ埋め込んでください。文字列連結で通すと、認可の仕組みが注入の入口になります。ORM 同士の比較は[TypeORMとPrismaの違いをまとめた記事](https://www.issoh.co.jp/tech/details/4331/)に譲ります。

## 2026年9月時点の版の状態：Prisma 8 RCとDrizzle 1.0 RC

両 ORM は移行期にあり、ドキュメントの記述とインストールで入る版がずれています。以下は2026年9月6日に npm レジストリの配布タグと公開日を実測した結果です。

| パッケージ                | latestが指す版  | 公開日        |
| -------------------- | ----------- | ---------- |
| `prisma`             | 8.0.0-rc.13 | 2026-09-04 |
| `@prisma/client`     | 7.10.0      | 2026-08-25 |
| `@prisma/adapter-pg` | 7.10.0      | 2026-08-25 |
| `drizzle-orm`        | 0.45.2      | 2026-03-27 |
| `drizzle-kit`        | 0.31.10     | 2026-03-17 |
| `supabase`           | 2.116.0     | 2026-08-26 |

### prismaのlatestタグが8.0のRCを指している状態の見分け方

実測時点で `prisma` の `latest` タグは `8.0.0-rc.13`（2026-09-04公開）を指していました。安定版の最新は `7.10.0`（2026-08-25公開）で、`prev` タグがこちら。`next` タグには `8.0.0-rc.10` が残っており、`latest` のほうが新しいという逆転も起きています。

厄介なのは `@prisma/client` と `@prisma/adapter-pg` の `latest` が `7.10.0` のままな点です。版を書かずに CLI とクライアントを同時に入れると、CLI だけ 8.0 系のリリース候補、クライアントは 7.10.0 という組み合わせになる。生成物の形式が食い違い、生成直後の型エラーとして表面化します。前掲の導入コマンドで版を完全指定したのはこのためです。

公式ドキュメント側も既定表示が Prisma 8 へ切り替わり、7 系の記述は `/orm/v7` 配下へ移動しました。手元に 7.10.0 を入れている場合、既定のページを読むと前提が1世代ずれます。[リポジトリ自体も prisma/prisma から prisma/orm へ移設](https://github.com/prisma/orm)され、旧URLは転送で届く状態です。

### drizzle-ormの安定版は0.45系で1.0はRCという二本立て

Drizzle 側は逆向きのずれ方です。実測では [drizzle-orm の配布タグ](https://www.npmjs.com/package/drizzle-orm)は `latest` が `0.45.2`（2026-03-27公開）、`drizzle-kit` の `latest` が `0.31.10`（2026-03-17公開）。一方 `beta` タグは `1.0.0-beta.22`、`rc` タグは `1.0.0-rc.4`（2026-06-27公開）に付いています。

公式ドキュメントのサンプルが 1.0 系の書き方に寄っている場合、`latest` で入れた 0.45 系には該当の API が無いことがあります。前掲の Supabase 向けガイドが `drizzle-orm@rc` を指定しているのはその表れで、ガイドどおりに写すとリリース候補が入る。ヘルパーや設定キーで型エラーになったら、入っている版とドキュメントの前提を突き合わせてください。

### 版を固定する運用：メジャー指定とロックファイルの扱い方の基本

対処は素朴です。依存の宣言では範囲指定を付けず完全指定にし、ロックファイルをコミットして継続的統合の環境ではロックファイルどおりに入れる。意図的に新しい版を取りに行く場合もメジャーを明示する。移行期の製品では `latest` が安定版を指すという暗黙の前提が崩れます。

```
# 配布タグの実状をその場で確認する
npm view prisma dist-tags
npm view @prisma/client dist-tags
npm view drizzle-orm dist-tags

# ロックファイルどおりに入れる（CI ではこちらを使う）
npm ci
```

## 採用条件と見送り場面：ORMを入れるかどうかを条件で切り分ける

ORM を足すかどうかは好みではなく条件で決まります。

### ORMを入れる3つの条件：整合性・型生成・移植性から判断する

第一に、複数テーブルにまたがる書き込みで不変条件を守る必要がある場合。自動生成 API は1リクエストが1トランザクションなので、2回呼びに分けると途中で失敗した状態が残ります。関数へ寄せる手もありますが、ロジックが増えるほどアプリ側の言語で書けるほうが保守しやすい。

第二に、スキーマをコードで管理して型を生成したい場合。定義を TypeScript 側に置けば、列の追加が型エラーとして落ちてきます。第三に、将来 Supabase 以外の Postgres へ出る可能性がある場合。台帳の一本化を含む移行設計や、既存データベースからの載せ替えについては[システムマイグレーション・リプレイス](https://www.issoh.co.jp/service/system/migration/)で相談を受けています。

### ORMを入れずに自動生成APIで押し切ってよい場面の見極め方

逆に、次が揃うなら ORM は不要です。読み書きが単一テーブル単位で収まる。認可を行レベルセキュリティに寄せきれる。画面数が少なく、型は生成済みの定義で足りる。この範囲で足すと、接続経路の設計とロール切り替えの実装という2つの負債を先に背負います。

エッジ前提の場合も慎重に。トランザクションプーラーの制約と ORM ランタイムの実行環境要件が重なるため、素直に自動生成 API を叩くほうが薄く済む。[自動生成APIとRPCの使い分けの記事](https://www.issoh.co.jp/tech/details/17070/)と合わせて読むと決めやすい。

### PrismaとDrizzleのどちらを選ぶか：常駐かエッジかで分ける

常駐するバックエンドがあり、人数が多く、書き味の統一を優先するなら Prisma です。スキーマ言語が独立しているぶん、SQL に慣れていない実装者でも同じ形に書けます。ただし v7 でアダプタ必須へ変わった直後で、版の状態も動いている点は織り込んでください。

エッジや軽量な実行環境が前提で、SQL に近い書き方を好み、ポリシーもコードで管理したいなら Drizzle。Supabase 向けのロール定義とヘルパーが公式にある点は、この用途では実装量の差になって出ます。決めきれない場合は、ポリシーをコードに置きたいかどうかで切ってください。

## よくある質問

### Prismaは何をするツールで、SQLを書かずに済みますか？

スキーマ定義から型付きクライアントを生成し、差分からマイグレーション SQL を作って適用するツールです。読み書きの大半はメソッド呼び出しで書けますが、行レベルセキュリティのポリシーや拡張のように宣言する機構が無い領域は SQL 側に残ります。本体は Apache License 2.0 のオープンソースとして公開されています。

### Prismaでデータベースに到達できないと出るのはなぜですか？

直結のホストは IPv6 が前提のため、IPv6 が出ていない実行環境からは文字列が正しくても到達しません。実行環境から IPv6 が引けるかを確認し、引けない場合は IPv4 アドオンを有効にするか、共有プーラーのセッションモード（5432）へ切り替えます。アドオンはデュアルスタックではないので、有効化後は IPv6 側の経路が使えなくなる点も見ておいてください。

### マイグレーションはトランザクションプーラー（6543）でも流せますか？

向きません。トランザクションモードは prepared statement を扱えず、複数文をまとめて流すスキーマ変更と噛み合いません。直結またはセッションモード（5432）で流してください。

### Prisma v7でdirectUrlはどこへ行きましたか？

`datasource` ブロックの `url`・`directUrl`・`shadowDatabaseUrl` は非推奨になり、設定リファレンスでは `datasource.directUrl` が v7 で削除済みと記載されています。CLI 側は `prisma.config.ts` の `datasource.url`、実行時はドライバアダプタの `connectionString` へ分かれました。

### npm installしたらprismaだけ8系のRCが入りました。どうすべきですか？

2026年9月6日の実測では `prisma` の `latest` が `8.0.0-rc.13`、`@prisma/client` の `latest` が `7.10.0` でした。CLI とクライアントの世代が割れると生成物の形式が食い違います。両方を同じ版で完全指定して入れ直し、`npm ls` で実際に入った版を確認してください。

### ORM経由のクエリでRLSが効いていないように見えます。なぜですか？

接続ユーザーが管理権限を持つ `postgres` で、対象テーブルの所有者にもなっているためです。PostgreSQL の既定では所有者はポリシーの評価を通りません。権限を絞った専用ロールでつなぎ、トランザクション内でロール切り替えとクレームの差し替えを挟んでください。

### 取り込んだSQLとORMのスキーマ、どちらを正にすべきですか？

片方に決め、もう片方は読み取り専用に落としてください。コードでスキーマを書く体制なら ORM を正にして生成物を supabase のマイグレーションフォルダへ出す。SQL や拡張を多用する体制なら CLI を正にして、ORM はイントロスペクションと型生成だけに使います。

## 関連記事

- [Supabase 自動生成API（PostgREST）とRPC](https://www.issoh.co.jp/tech/details/17070/)：ORM を入れずに押し切る場合の対になる選択肢
- [Supabase CLIとローカル開発環境](https://www.issoh.co.jp/tech/details/17063/)：マイグレーション台帳を CLI 側へ寄せるときの手順
- [Supabase RLSの設定手順とポリシー設計](https://www.issoh.co.jp/tech/details/17056/)：本記事で扱ったロール切り替えの前段になるポリシー本体
- [Supabase Next.js連携の実装](https://www.issoh.co.jp/tech/details/17065/)：生成したクライアントをアプリのどこへ置くか
- [Supabase APIキーの管理](https://www.issoh.co.jp/tech/details/17068/)：anon と service\_role の使い分け
- [Drizzle ORM（ドリズル）とは](https://www.issoh.co.jp/tech/details/7939/)：スキーマ定義とクエリの書き方
- [Prismaバージョン完全ガイド](https://www.issoh.co.jp/tech/details/9996/)：v7 までの変更点と移行時の注意
- [TypeORMとPrismaの違いと選び方](https://www.issoh.co.jp/tech/details/4331/)：Node.js の ORM を横並びで比較
- [SupabaseとFirebaseの違いを徹底比較](https://www.issoh.co.jp/column/details/3002/)：製品としての全体像から入る場合

---

出典: [SupabaseにPrismaとDrizzleをつなぐ：接続文字列とマイグレーション二重管理の設計](<https://www.issoh.co.jp/tech/details/17072/>)（株式会社一創）
