# Prisma ORM 本番運用ガイド（v7）：Rustフリー化・driverAdapters・型安全なスキーマからマイグレーション・トランザクション・サーバーレスまで

> Prisma ORM（v7）を本番運用する実装ガイド。新generator『prisma-client』とdriver adapter必須化、スキーマからの型生成、CRUD・リレーション（N+1回避）・トランザクション・冪等性、prisma migrate dev/deploy、$queryRawの安全な使い方、Client Extensions、Next.js/サーバーレスの接続管理、Prisma Postgres/Accelerate、そしてDrizzleとの使い分けとv6→v7移行までを、すべて公式ドキュメントに忠実な実コードで解説します。

- 公開日: 2026-06-26
- 著者: 友田 陽大
- タグ: TypeScript, Prisma, PostgreSQL, 型安全, アーキテクチャ設計
- URL: https://tomodahinata.com/blog/prisma-orm-production-guide-type-safe-database-v7-driver-adapters

## 要点

- Prisma v7はRustエンジンを廃し、generatorが『prisma-client』へ。outputで生成先を明示し、全DBでdriver adapter（PostgreSQLは@prisma/adapter-pg）が必須になった
- スキーマが唯一の真実源。modelから型・クライアント・マイグレーションが導出され、Prisma.UserGetPayloadとsatisfiesで結果型まで機械生成する（手書きDTO/asキャスト禁止）
- リレーションはinclude/selectで取得し、行ごとに追加発行されないためN+1が出ない。トランザクションは$transactionの配列形と対話形を使い分け、外部API呼び出しは境界の外へ
- マイグレーションは開発がmigrate dev（シャドウDB必須・破壊的）、本番CIはmigrate deploy（保留分のみ適用）。db pushは履歴を残さない使い捨て検証専用
- 生SQLは$queryRawのタグ付きテンプレートで必ずパラメータ化し、$queryRawUnsafe/Prisma.rawにユーザー入力を渡さない。サーバーレスはクライアントをハンドラ外で生成し接続を枯渇させない

---

「データアクセスを型安全にしたい」——要件としては一行です。けれど本番へ載せようとした瞬間、判断すべきことが一気に増えます。**スキーマをどこで宣言するのか。型はどこから来るのか。リレーションでN+1を出さないには。トランザクション境界はどこに引くのか。マイグレーションをどう生成・レビュー・適用するのか。SQLインジェクションをどう構造的に防ぐのか。サーバーレスでコネクションをどう枯渇させないのか。**

この記事は、**Prisma ORM** を **v7** 世代で**本番品質**に運用するための実装ガイドです。Prisma は「**スキーマを唯一の真実源（single source of truth）にして、そこから型・クライアント・マイグレーションをすべて導出する**」という思想のORMで、宣言的なデータモデルと開発体験の良さで広く使われています。そして 2025年11月の **v7** で、内部アーキテクチャが大きく変わりました——長らく同梱されていた **Rust製クエリエンジンが廃止**され、**driver adapter が必須**になり、クライアントの生成方式（generator）も新しくなっています。本記事はこの **v7 を前提**に、公式ドキュメントに忠実な事実を土台として、私が普段から徹底している「**実行時の正しさを、できる限り型に押し込む**」設計思想を重ねて解説します。

> **この記事のルール**：API仕様・コマンド・コードは **Prisma 公式ドキュメント（2026年6月時点、v7系）** に基づきます。Prisma は活発に進化しており、generator・adapter・CLI フラグは版で変わります（本記事の `prisma-client` generator・driver adapter 必須化・`prisma.config.ts` はいずれも v7 で**既定**になった挙動です）。本番投入前に必ず[公式ドキュメント](https://www.prisma.io/docs/orm)で最新仕様を確認してください。DB接続情報（接続文字列・認証情報）は**環境変数前提**で扱います（ハードコード厳禁）。一部に「Preview（プレビュー）」段階の機能が含まれる箇所は明記します。

---

## 0. メンタルモデル：ORM境界も「型」で固める

本題の前に、この記事を貫く一本の軸を共有させてください。

私は型安全を徹底したサブスク課金基盤の開発で、`as` / `any` / `enum` / non-null assertion を**全面禁止**しました。代わりに Union 型＋`satisfies`＋`NeverError(value: never)` を組み合わせ、「**分岐の取りこぼしがあればコンパイルが落ちる**」状態を作り、外部入力の境界はすべて Zod で検証しました。

この思想——「**ランタイムの不正を、できるだけコンパイル時に倒す**」——は、ORM境界（DBアクセス）にもそのまま当てはまります。むしろデータベースは、アプリの中で**最も型が崩れやすい境界**です。`SELECT *` の結果が `any` のまま流れてくれば、その先のロジックはいくら丁寧に書いても砂上の楼閣になります。

Prisma のメンタルモデルはこうです。

> **`schema.prisma` でデータモデルを宣言する → `prisma generate` でスキーマに対応する型付きクライアントが生成される → クエリの入力も結果も型が通る = スキーマが唯一の真実源。**

スキーマが single source of truth になり、そこから型もクライアントもマイグレーションも導出される。これが Prisma の中核であり、私の設計原則とまっすぐ噛み合う理由です。Prisma と Drizzle の最大の違いは「**スキーマを独自DSL（`.prisma`）で書くか、TypeScript で書くか**」という点に集約されますが（後述の §13 で使い分けを整理します）、「スキーマから型を機械生成する」という規律はどちらも共通です。

---

## 1. v7 で何が変わったのか（最初に押さえる）

Prisma を以前から使っている人ほど、まずここを読んでください。v7 は**セットアップが変わった**ため、古い記事のコードがそのままでは動きません。

| 領域 | v6（旧） | v7（現行） |
| --- | --- | --- |
| generator | `prisma-client-js`（`node_modules` に生成） | **`prisma-client`**（`output` で**生成先を明示**） |
| クエリエンジン | Rust製バイナリを同梱 | **Rustフリー**（TypeScript/WASM のクエリコンパイラ） |
| driver adapter | 任意（Preview） | **全DBで必須**（PostgreSQLは `@prisma/adapter-pg`） |
| ミドルウェア `$use` | あり | **削除** → Client Extensions（`$extends`）へ |
| `Prisma.validator` | あり | **レガシー扱い** → TypeScript の `satisfies` へ |
| 設定の置き場所 | `package.json` の `"prisma"` キー | **`prisma.config.ts`**（`defineConfig`） |
| import 元 | `@prisma/client` | **生成先パス**（例：`../generated/prisma/client`） |

Rustフリー化は単なる内部刷新ではなく、運用に効く実利があります。公式ブログによれば、新アーキテクチャは**クエリが最大 3.4 倍高速**になり、**バンドルサイズが約 14MB → 1.6MB（約90%減）**まで縮みました。ネイティブバイナリ依存が消えることで、サーバーレス/エッジへのデプロイが大幅に単純化されます——コールドスタートとアーティファクトサイズが効く環境ほど恩恵が大きい変更です。

> **設計判断**：新規プロジェクトは迷わず v7（`prisma-client` generator）で始めるべきです。既存の v6 プロジェクトは、公式の[v7アップグレードガイド](https://www.prisma.io/docs/guides/upgrade-prisma-orm/v7)に沿って、generator・adapter・import パスを移行します（§14 で要点をまとめます）。

---

## 2. セットアップ：generator・driver adapter・prisma.config.ts

### 2.1 インストール

PostgreSQL を題材にします。クエリエンジンが無くなった代わりに、**接続を担う driver adapter** を明示的に入れます。

```bash
npm install @prisma/client @prisma/adapter-pg
npm install -D prisma tsx
```

### 2.2 スキーマと generator

`prisma/schema.prisma` の冒頭で、データソースと **新しい generator** を宣言します。v7 では `provider = "prisma-client"`、そして `output`（生成先）が**必須**です。

```prisma
// prisma/schema.prisma
datasource db {
  provider = "postgresql"
}

generator client {
  provider     = "prisma-client"      // v7の新generator（旧: prisma-client-js）
  output       = "../src/generated/prisma" // 生成先を明示（node_modulesには出ない）
  runtime      = "nodejs"             // deno / bun / workerd / vercel-edge なども選べる
  moduleFormat = "esm"                // esm | cjs
}
```

ここが v7 の最大の変化点です。**生成されたクライアントは `node_modules` ではなく、あなたのリポジトリ内（`output` のパス）に出力されます。**つまり生成物が「自分のコードの一部」として可視化され、import 元も `@prisma/client` ではなく**生成先パス**になります。`runtime` を切り替えるだけで Bun・Deno・Cloudflare Workers・Vercel Edge 向けのクライアントを生成できるのも、Rustフリー化（＝ネイティブバイナリ非依存）の果実です。

> **運用のコツ**：`output` で指定したディレクトリ（例 `src/generated/`）は**生成物なので `.gitignore` に入れ**、CI とデプロイのビルド前に必ず `prisma generate` を走らせます（`postinstall` か明示的なビルドステップで）。生成物をコミットするか否かはチーム方針ですが、少なくとも「生成し忘れたまま本番に出る」事故をビルド手順で塞ぐことが重要です。

### 2.3 prisma.config.ts（v7の設定起点）

v7 では、CLI がスキーマ・マイグレーション・シードをどう扱うかを **`prisma.config.ts`**（プロジェクト直下）で型安全に宣言します。環境変数は**自動では読み込まれない**ため、`dotenv` 等で明示的に読み込みます。

```ts
// prisma.config.ts（プロジェクト直下）
import "dotenv/config";
import { defineConfig, env } from "prisma/config";

export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
    seed: "tsx prisma/seed.ts", // シードは migrations.seed 配下
  },
  datasource: {
    url: env("DATABASE_URL"),
  },
});
```

設定がコードになることで、**モノレポでもデプロイでも再現性が担保**され、`package.json` の文字列キーに依存していた頃の曖昧さが消えます。

### 2.4 PrismaClient の生成（adapter 必須）

v7 では、**クライアント生成時に driver adapter を渡すことが必須**です。これがアプリ全体で使い回す「DBハンドル」になります。

```ts
// src/db.ts
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "./generated/prisma/client"; // ← 生成先からimport

const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });

export const prisma = new PrismaClient({ adapter });
```

`PrismaClient` を `@prisma/client` からではなく**生成先 `./generated/prisma/client` から import** している点に注目してください。v7 を語る記事はここでつまずきがちです。接続プール等のチューニングは、接続文字列のクエリパラメータではなく**adapter 側のオプション**で行う方向に整理されました（プールの既定値も v7 で見直されています）。

---

## 3. スキーマ・アズ・コード：model で宣言する

Prisma のスキーマは、`model` ブロックで「データの形」を宣言します。これが**型・クライアント・マイグレーションすべての源泉**です。

```prisma
model User {
  id      Int      @id @default(autoincrement())
  email   String   @unique
  name    String?
  role    Role     @default(USER)
  posts   Post[]
  profile Profile?
}

model Profile {
  id     Int    @id @default(autoincrement())
  bio    String
  user   User   @relation(fields: [userId], references: [id])
  userId Int    @unique
}

model Post {
  id        Int       @id @default(autoincrement())
  title     String
  published Boolean   @default(false)
  author    User      @relation(fields: [authorId], references: [id])
  authorId  Int
  comments  Comment[]
  createdAt DateTime  @default(now())

  @@index([authorId, title])
}

model Comment {
  id      Int    @id @default(autoincrement())
  content String
  post    Post   @relation(fields: [postId], references: [id])
  postId  Int

  @@map("comments") // 物理テーブル名を comments に
}

enum Role {
  USER
  ADMIN
}
```

読み解きの要点を、設計の観点から押さえます。

- **`@id @default(autoincrement())`** は主キーと自動採番。`@unique` は一意制約。`String?` の `?` は NULL 許容（=`name: string | null`）を意味します。
- **リレーションは2つで1組**です。`Post` 側の `author User @relation(fields: [authorId], references: [id])` が外部キー側、`User` 側の `posts Post[]` が逆参照。`Profile.userId` に `@unique` を付けることで「1ユーザーにつき1プロフィール」という**1対1**が型レベルで表現されます。
- **`@@index([authorId, title])`** は複合インデックス、**`@@map("comments")`** はモデル名（コード上）と物理テーブル名（DB上）を分離します。コードは `Comment`、DBは `comments`——この分離は既存DBへの後付け導入で効きます。
- **`enum`** は DB の列挙型に対応し、`Role.USER` のように型安全に扱えます（私は手書きの文字列リテラル enum を避け、こうしてスキーマ駆動にします）。

> **設計のコツ（SRP）**：スキーマは「データの形」という1つの責務に徹します。集計用のビューやアプリ固有の派生値をここに混ぜず、ビジネスロジックはアプリ層へ。スキーマが肥大化したら、Prisma は複数ファイルへの分割もサポートします。

スキーマを書き換えたら **`npx prisma generate`** で型付きクライアントを再生成します（後述のマイグレーションコマンドは生成も兼ねます）。

---

## 4. 型安全：結果型は「推論させる」、名付けない

ここが Prisma の真価です。スキーマから、入力型も結果型も**機械生成**されます。**手書きの DTO 型や `as` キャストでこの境界をまたぐのは、型安全を自ら捨てる行為**です。

```ts
import { Prisma } from "./generated/prisma/client";

// 1) 入力型：スキーマの制約（idは自動採番なので不要、nameは任意）が型に反映される
const data: Prisma.UserCreateInput = {
  email: "hinata@example.com",
  posts: { create: { title: "Hello Prisma v7" } },
};

// 2) 結果型：include/select の「形」から正確な戻り型を導出する
const userWithPosts = { include: { posts: true } } satisfies Prisma.UserDefaultArgs;
type UserWithPosts = Prisma.UserGetPayload<typeof userWithPosts>;
// UserWithPosts は { id; email; name: string | null; ...; posts: Post[] } に正確に推論される
```

ポイントは2つです。

1. **`Prisma.UserCreateInput`** のような生成型を使えば、「INSERT 時に何を渡せて何を渡せないか」がスキーマと完全に一致します。`id` は `autoincrement()` なので渡せない、`name` は `?` なので省略できる——この差分を人間が同期し続ける必要がありません（DRY 違反の典型を構造的に排除）。
2. **`Prisma.UserGetPayload<typeof args>`** は、`include` / `select` で指定した「取得する形」から**正確な結果型**を導出します。`posts` を含めたなら結果型にも `posts` が現れ、含めなければ現れない。これを `satisfies`（TypeScript ネイティブ演算子）と組み合わせるのが v7 推奨の作法です。

> **v7 の注意**：旧来の `Prisma.validator<...>()` は**レガシー（`prisma-client-js` 専用）**で、新しい `prisma-client` generator では使えません。代わりに上記の **`satisfies` + `GetPayload`** を使ってください。「**境界の型は推論させ、自分で名付けない**」——これが Prisma を使ううえでの第一の規律です。

---

## 5. CRUD：基本操作を一望する

`prisma.<model>.<操作>` という統一されたAPIです。`where` / `select` / `orderBy` / `take` / `skip` など、引数の名前と意味がモデル横断で一貫しているのが Prisma の学習効率の高さです。

```ts
// create
await prisma.user.create({
  data: { email: "elsa@prisma.io", name: "Elsa" },
});

// createMany（重複は skipDuplicates で握りつぶせる）
await prisma.user.createMany({
  data: [
    { email: "bob@prisma.io", name: "Bob" },
    { email: "yewande@prisma.io", name: "Yewande" },
  ],
  skipDuplicates: true,
});

// findUnique（一意キーでの単一取得）
const user = await prisma.user.findUnique({ where: { email: "elsa@prisma.io" } });

// findMany（条件・並び・ページング・必要な列だけ）
const users = await prisma.user.findMany({
  where: { email: { endsWith: "@prisma.io" } },
  select: { id: true, email: true }, // 取りすぎ防止（コスト＆機密の最小化）
  orderBy: { id: "asc" },
  take: 10,
  skip: 20,
});

// update / upsert / delete
await prisma.user.update({
  where: { email: "elsa@prisma.io" },
  data: { name: "Elsa the Great" },
});

await prisma.user.upsert({
  where: { email: "viola@prisma.io" },
  update: { name: "Viola v2" },
  create: { email: "viola@prisma.io", name: "Viola" },
});

await prisma.user.delete({ where: { email: "bob@prisma.io" } });
```

実務上のコツを3つ。

- **`select` で取る列を絞る**のは、パフォーマンスと**機密の最小化**の両面で効きます。`password_hash` のような列を「`select` しなければ漏れない」状態に保つのは、安全側に倒れる良い既定です。
- **`upsert`** は `where`（探す）・`update`（在れば）・`create`（無ければ）の3点セット。これは後述の冪等性設計の主役になります。
- **ページング**は `take`/`skip`（オフセット）と `cursor`（カーソル）の2方式。大きなテーブルではオフセットは深いページで劣化するため、`cursor: { id: lastId }` + `orderBy` のカーソル方式を選びます。

---

## 6. リレーションと N+1：include / select で「1回で」取る

ORM で最も事故が多いのが **N+1 問題**（一覧を取ってから1件ずつ関連を引き、クエリが行数分に膨れる）です。Prisma では関連を **`include`（丸ごと）/ `select`（選んで）** で宣言的に取得し、**行ごとに追加クエリを発行しない**ため、素直に書けば N+1 が出ません。

```ts
// include：関連レコードを丸ごと
const usersWithPosts = await prisma.user.findMany({
  include: { posts: true },
});

// select：ネストして、必要な列だけ
const slim = await prisma.user.findMany({
  select: {
    name: true,
    posts: { select: { title: true } },
  },
});
```

**ネストした書き込み**も強力です。親と子を**1トランザクションで**作れます（明示的な `$transaction` 不要——公式は「ネスト書き込みは単一トランザクションで複数操作を行う」と定義しています）。

```ts
// 親ユーザーと記事を同時に作成
await prisma.user.create({
  data: {
    email: "vlad@prisma.io",
    posts: { create: [{ title: "First" }, { title: "Second" }] },
  },
});

// 既存を connect、無ければ作る connectOrCreate
await prisma.post.create({
  data: {
    title: "How to make croissants",
    author: {
      connectOrCreate: {
        where: { email: "viola@prisma.io" },
        create: { email: "viola@prisma.io", name: "Viola" },
      },
    },
  },
});
```

> **発展（Preview 機能）**：取得時の関連ロード戦略を選ぶ `relationLoadStrategy: "join" | "query"` があります。`"join"` は DB 側の `LATERAL JOIN`（PostgreSQL）等で**単一クエリ化**し、`"query"` は複数クエリをアプリ側で結合します。**本機能は執筆時点で Preview** のため、本番採用は機能フラグと検証を前提にしてください。いずれにせよ、`include`/`select` は行数に比例してクエリが増えないので、まず素直に書けば N+1 は回避できます。

---

## 7. トランザクションと冪等性：境界をコードで固める

決済や在庫のような「**正しさ**」が要る処理は、運用の注意深さではなく**コードの構造**で守ります。Prisma のトランザクションは2系統です。

### 7.1 配列形（独立した複数クエリをまとめてアトミックに）

```ts
const [posts, total] = await prisma.$transaction([
  prisma.post.findMany({ where: { title: { contains: "prisma" } } }),
  prisma.post.count(),
]);
```

### 7.2 対話形（途中の値で分岐する一連の処理）

```ts
import { Prisma } from "./generated/prisma/client";

await prisma.$transaction(
  async (tx) => {
    const sender = await tx.account.update({
      where: { email: "alice@prisma.io" },
      data: { balance: { decrement: 100 } },
    });
    if (sender.balance < 0) throw new Error("Insufficient funds"); // throwで全ロールバック
    await tx.account.update({
      where: { email: "bob@prisma.io" },
      data: { balance: { increment: 100 } },
    });
  },
  {
    maxWait: 5_000,  // トランザクション開始を待つ上限
    timeout: 10_000, // トランザクション全体の上限
    isolationLevel: Prisma.TransactionIsolationLevel.Serializable, // 競合に強い分離レベル
  },
);
```

実務で外せない規律を3つ。

- **トランザクション内では必ず `tx` を使う**。`tx` ではなく外側の `prisma` を呼ぶと、その操作はトランザクションの外で走り、原子性が壊れます。
- **外部API呼び出し（決済プロバイダ等）はトランザクション境界の外へ**。ネットワークI/Oをトランザクション内に抱えると、DBの接続とロックを長時間占有し、タイムアウトとデッドロックの温床になります。
- **冪等性は `upsert` と一意制約で作る**。リトライや重複配信を前提に、`@unique` な冪等キー（`idempotencyKey` 等）で `upsert` すれば、「同じ操作が2回来ても結果が1回分」になります。重複時の `P2002`（一意制約違反）を捕まえて握りつぶすのも常套手段です（§9）。

> **関連**：冪等性・二重課金対策の設計思想は、決済を題材にした別記事で深掘りしています。本番二重課金0件を支えた考え方は、ORM が Prisma でも Drizzle でも、また DynamoDB でも普遍です。

---

## 8. 生SQLとセキュリティ：必ずパラメータ化する

ORM で表現しづらい集計やDB固有機能には生SQLを使いますが、ここが**SQLインジェクションの最大の入口**です。Prisma の安全な書き方は**タグ付きテンプレート**（`$queryRaw` / `$executeRaw`）です。`${}` は文字列連結ではなく**バインドパラメータ（プリペアドステートメント）**になり、注入が構造的に起きません。

```ts
// ✅ 安全：${email} はパラメータとして束縛される
const email = "emelie@prisma.io";
const rows = await prisma.$queryRaw`SELECT id, name FROM "User" WHERE email = ${email}`;

// ✅ IN 句は Prisma.join で安全に展開
import { Prisma } from "./generated/prisma/client";
const ids = [1, 3, 5, 10];
await prisma.$queryRaw`SELECT * FROM "User" WHERE id IN (${Prisma.join(ids)})`;

// ✅ 条件付き断片は Prisma.sql / Prisma.empty で組み立てる
const name: string | null = null;
await prisma.$queryRaw`
  SELECT * FROM "User"
  ${name ? Prisma.sql`WHERE name = ${name}` : Prisma.empty}
`;

// ✅ 件数を返す書き込み系は $executeRaw（影響行数を number で返す）
const affected: number = await prisma.$executeRaw`
  UPDATE "User" SET active = ${true} WHERE "emailValidated" = ${true}
`;
```

逆に、**やってはいけない**形がこれです。

```ts
// ❌ 厳禁：文字列連結はインジェクションの穴。ユーザー入力を絶対に渡さない
const input = '"x" UNION SELECT id, title FROM "Post"';
await prisma.$queryRawUnsafe("SELECT id, name FROM \"User\" WHERE name = " + input);
```

`$queryRawUnsafe` と `Prisma.raw(...)` は「**エスケープされない生の断片**」を差し込むAPIで、ユーザー入力を渡せば即座に脆弱性になります。やむを得ず使う場合でも、入力が**自分の管理下の固定値**であることを保証し、外部入力は必ずパラメータ側（`${}` / `Prisma.join`）に置きます。なお Prisma は型安全に生SQLを書く **TypedSQL** も提供しており、生SQLが必要な場面ではまずこちらの採用を検討する価値があります。

---

## 9. エラーハンドリング：コードで分岐する

Prisma の既知エラーは **`Prisma.PrismaClientKnownRequestError`** として投げられ、`code` で原因を判別できます。v7 では `Prisma` 名前空間も**生成先パスから import** します。

```ts
import { Prisma } from "./generated/prisma/client";

try {
  await prisma.user.create({ data: { email: "dup@example.com" } });
} catch (e) {
  if (e instanceof Prisma.PrismaClientKnownRequestError) {
    if (e.code === "P2002") {
      // 一意制約違反：冪等な再試行なら 200/既存を返す、UI なら「既に登録済み」
      return { ok: false, reason: "duplicate" as const };
    }
    if (e.code === "P2025") {
      // 対象レコードが無い（update/delete の前提が崩れた）
      return { ok: false, reason: "not_found" as const };
    }
  }
  throw e; // 想定外は握りつぶさず再送出（可観測性のため）
}
```

代表的なコードは **`P2002`（一意制約違反）** と **`P2025`（対象レコードが見つからない）** です。これらを**型付きの結果（Union）に翻訳**し、呼び出し側で `switch` 漏れがあればコンパイルが落ちる——という設計にすると、エラー処理が「祈り」から「保証」に変わります。想定外のエラーまで握りつぶさないこと（再送出してログ/トレースに残す）も、可観測性の観点で重要です。

---

## 10. Client Extensions：横断的関心を型安全に足す（v7でミドルウェア廃止）

v7 で**ミドルウェア（`$use`）は削除**され、代わりに **Client Extensions（`$extends`）** が正式な拡張点になりました。`model` / `client` / `query` / `result` の4種で、計算列の付与やクエリのロギングなどを型安全に差し込めます。

```ts
// query 拡張：全モデル・全操作の所要時間をログ（可観測性）
const prisma = basePrisma.$extends({
  query: {
    $allModels: {
      async $allOperations({ model, operation, args, query }) {
        const start = performance.now();
        const result = await query(args);
        const ms = Math.round(performance.now() - start);
        console.log(JSON.stringify({ event: "db_query", model, operation, ms }));
        return result;
      },
    },
  },
});
```

ミドルウェアと違い、Extensions は**拡張ごとに新しいクライアントを返す**ため、適用範囲を限定でき、型も保たれます。ログは相関ID付きの構造化ログにして、トレースと突き合わせられる形にしておくのが本番運用の定石です。

---

## 11. マイグレーション：dev と deploy を正しく分ける

スキーマ変更を**版管理**し、安全に本番へ適用するのが Prisma Migrate です。**コマンドの役割分担を間違えると本番でデータを失います**——ここは強調します。

```bash
# 開発：差分から新しいマイグレーションを生成して即適用（＋クライアント再生成）
npx prisma migrate dev --name add_user_role

# 本番/CI：保留中のマイグレーションだけを順に適用（生成もリセットもしない）
npx prisma migrate deploy

# 試作のみ：マイグレーション履歴を残さずスキーマをDBへ同期（使い捨て）
npx prisma db push
```

| コマンド | 用途 | 重要な性質 |
| --- | --- | --- |
| `migrate dev` | **開発専用** | 差分検出のため**シャドウDBが必要**。ドリフト検出時に**DBをリセット（＝データ破棄）し得る**。本番で打ってはいけない |
| `migrate deploy` | **本番/CI** | 保留中のマイグレーションのみ適用。**ドリフト検査もリセットもシャドウDBも使わない**ので安全 |
| `db push` | 試作・スキーマ実験 | `_prisma_migrations` を作らず履歴も残さない。**版管理が不要な使い捨て検証専用** |

**シャドウDB**は、`migrate dev` がスキーマのドリフトやデータ損失を検出するために**自動で作成・削除する一時DB**です。本番には不要（`migrate deploy` は使わない）ですが、開発環境/CIでマイグレーションを生成する際に接続権限が要る点に注意します。

> **本番フロー**：開発で `migrate dev` してマイグレーションファイルを生成 → **生成された SQL を必ず人間がレビュー**（破壊的変更がないか）→ コミット → CI が `migrate deploy` で本番に適用。無停止スキーマ変更（ロックを避けるDDL、列追加→バックフィル→制約付与の段階適用）は、生成されたSQLをこのレビュー段で点検して担保します。

---

## 12. サーバーレス／Next.js：接続を枯渇させない

Prisma の本番事故で最も多いのが **接続枯渇**です。サーバーレスは関数インスタンスが大量に立ち上がり、各々が DB 接続を握ると、PostgreSQL の `max_connections` をあっという間に食い潰します。Rustフリー化でアーティファクトは軽くなりましたが、**接続管理の原則は変わりません**。

### 12.1 開発のホットリロード対策（シングルトン）

Next.js の `next dev` は再読み込みのたびに `PrismaClient` を再生成しがちで、接続が増殖します。`globalThis` に保持して使い回します。

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

const createClient = () =>
  new PrismaClient({ adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }) });

const globalForPrisma = globalThis as unknown as { prisma?: ReturnType<typeof createClient> };

export const prisma = globalForPrisma.prisma ?? createClient();

// 本番ではグローバルに保持しない（コールドスタートごとに1個で十分）
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
```

### 12.2 サーバーレス本番の原則

- **クライアントはハンドラの外で生成**して、ウォームなインスタンス間で再利用する。
- **`$disconnect()` を毎リクエストで呼ばない**。接続の確立・破棄を繰り返すと逆にコストになります。
- **外部プーラを噛ませる**。多数の関数が直接 DB を叩く構成では、PgBouncer などのプーラ、あるいは後述の Prisma Accelerate / Prisma Postgres を前段に置きます。

### 12.3 Prisma Postgres / Accelerate（マネージドな接続プールとキャッシュ）

**Prisma Postgres** は、**PgBouncer による接続プールを内蔵**したマネージド PostgreSQL で、サーバーレス/エッジでの接続枯渇を構成側で解消してくれます（プールやエッジ対応は通常の接続文字列で自動的に効きます）。**Prisma Accelerate** は、外部DBにも使えるグローバルな接続プール＋クエリキャッシュのレイヤで、`@prisma/extension-accelerate` の `withAccelerate()` を `$extends` し、クエリ単位で `cacheStrategy` を指定できます。

```ts
import { withAccelerate } from "@prisma/extension-accelerate";

const prisma = basePrisma.$extends(withAccelerate());

// クエリ単位でキャッシュ（ttl=鮮度、swr=stale許容秒）
const posts = await prisma.post.findMany({
  where: { published: true },
  cacheStrategy: { ttl: 60, swr: 10 },
});
```

> **注意**：Accelerate の正確な接続文字列・初期化（特に v7 の driver adapter との併用）は版で細部が動くため、本番投入前に必ず[公式ドキュメント](https://www.prisma.io/docs/accelerate)で最新の手順を確認してください。`cacheStrategy` はキャッシュ可能な読み取りにのみ使い、整合性が要る読み取りには付けない、という線引きが重要です。

---

## 13. Prisma か Drizzle か：使い分けの軸

「型安全な TypeScript ORM」という土俵で必ず比較されるのが Drizzle です。私は両方を本番で使いますが、選定軸はシンプルです。

| 観点 | Prisma | Drizzle |
| --- | --- | --- |
| スキーマの書き方 | 独自DSL（`schema.prisma`）。宣言的で読みやすい | TypeScript（`pgTable`）。学習対象が増えない |
| 抽象度 | 高め。リレーション取得が宣言的で楽 | 低め。SQLに近く透明性が高い |
| 型の源泉 | スキーマ → `prisma generate` で生成 | スキーマ（TS）→ `$inferSelect` で推論 |
| マイグレーション | Prisma Migrate（成熟・運用機能が厚い） | drizzle-kit（生成→適用がシンプル） |
| エッジ/軽量性 | v7 でRustフリー化し大幅改善 | もとから軽量・依存が薄い |
| マネージド連携 | Prisma Postgres / Accelerate が強い | アダプタで各社サーバーレスDBに対応 |

**ざっくりの指針**：

- **宣言的なデータモデルと厚いマイグレーション運用、マネージドな接続プール/キャッシュ込みで素早く本番化したい** → **Prisma**。
- **SQL の透明性を保ちたい・依存を最小にしたい・生成物より推論で完結させたい** → **Drizzle**。

どちらも「スキーマを唯一の真実源にして型を機械生成する」点は同じで、**`as`/`any` で境界をまたがない規律**さえ守れば、本番品質に到達できます。プロジェクトの制約（既存DB・チームのSQL習熟度・デプロイ先）で選べばよく、宗教戦争にする必要はありません。

---

## 14. v6 → v7 移行の要点

既存プロジェクトを v7 へ上げる際の最小チェックリストです（詳細は公式の[v7アップグレードガイド](https://www.prisma.io/docs/guides/upgrade-prisma-orm/v7)）。

1. **generator を `prisma-client` に変更**し、**`output` を必須指定**する（`node_modules` 生成からの脱却）。
2. **driver adapter を導入**（PostgreSQLは `@prisma/adapter-pg`）し、**`new PrismaClient({ adapter })`** に書き換える。
3. **import 元を生成先パスに変更**（`@prisma/client` → `./generated/prisma/client`）。`Prisma` 名前空間・エラー型も同様。
4. **`$use` ミドルウェアを Client Extensions（`$extends`）へ移植**する。
5. **`Prisma.validator` を `satisfies` + `GetPayload` へ置換**する。
6. **設定を `prisma.config.ts`（`defineConfig`）へ移す**。`--skip-generate` / `--skip-seed` 等の一部CLIフラグは廃止された点も確認する。
7. CI/デプロイのビルド前に **`prisma generate`** が走ることを保証し、`migrate deploy` で本番適用する。

段階移行の鉄則は「**まず動く形に直してから、最適化する**」です。`output`・adapter・import の3点を直せばまず動き、Extensions 化や Accelerate 導入は後追いで構いません。

---

## 15. 本番投入チェックリスト

最後に、Prisma を本番へ載せる前に私が必ず確認する項目です。

- [ ] generator は `prisma-client`、`output` を明示し、生成物は `.gitignore` ＋ ビルドで `prisma generate` を強制
- [ ] `PrismaClient` は driver adapter 付きで生成し、**アプリ全体で1インスタンス**（サーバーレスはハンドラ外）
- [ ] 接続文字列・認証情報は**環境変数のみ**（ハードコード・ログ出力なし）
- [ ] 結果型は `GetPayload` / `satisfies` で**推論**。`as`/`any` でDB境界をまたがない
- [ ] 一覧×関連は `include`/`select` で取得し、**N+1 が出ていないか**をクエリログで確認
- [ ] 「正しさ」が要る処理は `$transaction`（対話形は `tx` のみ使用、外部I/Oは境界外、必要なら `isolationLevel`）
- [ ] 冪等性は `@unique` キー＋`upsert`、`P2002`/`P2025` を型付き結果に翻訳
- [ ] 生SQLは `$queryRaw` のタグ付きテンプレートで**必ずパラメータ化**。`$queryRawUnsafe`/`Prisma.raw` にユーザー入力を渡さない
- [ ] マイグレーションは開発 `migrate dev` → **SQLレビュー** → CI `migrate deploy`。`db push` は本番で使わない
- [ ] サーバーレスは接続枯渇対策（プーラ／Prisma Postgres／Accelerate）と `cacheStrategy` の整合性線引き
- [ ] クエリ所要時間を Client Extensions で構造化ログ化し、トレースと相関

---

## まとめ

Prisma v7 は、Rustエンジンの廃止と driver adapter の必須化によって「**軽くて速く、サーバーレス/エッジに素直に乗る**」ORM へと一段進化しました。セットアップは変わりましたが、本質は一貫しています——**スキーマを唯一の真実源にし、型・クライアント・マイグレーションをそこから導出し、境界を `as`/`any` でまたがず、正しさをコードの構造（トランザクション・冪等性・パラメータ化）で保証する**。この規律さえ守れば、Prisma は「速く書けて、本番で壊れない」データアクセス層になります。

「**型安全なデータ層を、本番品質で素早く立ち上げたい**」——もしそんな要件をお持ちなら、Prisma/Drizzle の選定からマイグレーション運用・サーバーレスの接続設計まで、実装に落とし込むお手伝いができます。
