高度な機能

Raw SQL、Client Extensions、Connection Pooling、Read Replica、 soft delete、ロギング、Edge / Serverless 対応まで。

$queryRaw / $executeRaw

Prisma の API で表現できない SQL を直接実行する。

テンプレートリテラル形式(推奨・自動エスケープ)

type Result = { id: string, title: string, rank: number }

const rows = await prisma.$queryRaw<Result[]>`
  SELECT id, title,
    ts_rank(to_tsvector('simple', title), plainto_tsquery('simple', ${query})) AS rank
  FROM "Post"
  WHERE to_tsvector('simple', title) @@ plainto_tsquery('simple', ${query})
  ORDER BY rank DESC
  LIMIT 20
`

動的なクエリ

// $queryRawUnsafe(SQL Injection 注意)
const tableName = "Post"  // ここをユーザ入力にしない!
await prisma.$queryRawUnsafe(
  `SELECT * FROM "${tableName}" WHERE id = $1`,
  id
)

// 安全な動的構築
const where = Prisma.sql`status = ${status}`
const order = published ? Prisma.sql`ORDER BY views DESC` : Prisma.empty

await prisma.$queryRaw<Post[]>`
  SELECT * FROM "Post" WHERE ${where} ${order}
`

Prisma.sql ヘルパ

$extends(Client Extensions)

Prisma Client を拡張する公式機構。$use(middleware)の後継で、 現代ではこちらが推奨。

カスタムメソッド

const xprisma = prisma.$extends({
  model: {
    user: {
      async signUp(email: string) {
        return xprisma.user.create({
          data: { email, profile: { create: {} } }
        })
      },
      async findActive() {
        return xprisma.user.findMany({ where: { lastLogin: { gte: new Date(Date.now() - 30 * 86400_000) } } })
      },
    }
  }
})

await xprisma.user.signUp("alice@example.com")
await xprisma.user.findActive()

計算フィールド(result)

const xprisma = prisma.$extends({
  result: {
    user: {
      fullName: {
        needs: { firstName: true, lastName: true },
        compute(user) {
          return `${user.firstName} ${user.lastName}`
        }
      }
    }
  }
})

const u = await xprisma.user.findUnique({ where: { id } })
u?.fullName  // 自動的に存在する

クエリのフック(query)

const xprisma = prisma.$extends({
  query: {
    $allModels: {
      async $allOperations({ model, operation, args, query }) {
        const start = performance.now()
        const result = await query(args)
        const ms = performance.now() - start
        console.log(`${model}.${operation} took ${ms.toFixed(1)}ms`)
        return result
      }
    }
  }
})

Soft Delete を実装

const xprisma = prisma.$extends({
  query: {
    post: {
      async findMany({ args, query }) {
        args.where = { ...args.where, deletedAt: null }
        return query(args)
      },
      async delete({ args, query }) {
        // 物理削除を論理削除に置換
        return prisma.post.update({
          where: args.where,
          data: { deletedAt: new Date() },
        }) as any
      }
    }
  }
})

Connection Pool

埋め込み Pool(既定)

各 PrismaClient が内部に Pool を持つ。connection_limit パラメータで上限を制御:

DATABASE_URL="postgresql://...?connection_limit=10&pool_timeout=10"

外部 Pool(Serverless 必須)

PgBouncer 経由(pgbouncer=true)

DATABASE_URL="postgresql://...:6543/db?pgbouncer=true"
DIRECT_URL="postgresql://...:5432/db"  # migrate 用

Prisma Accelerate

公式 SaaS(HTTP ベースのコネクションプーラー + クエリキャッシュ)。 Vercel / Cloudflare Workers / Edge で長距離レイテンシを大幅短縮

npm install @prisma/extension-accelerate
import { PrismaClient } from "@prisma/client"
import { withAccelerate } from "@prisma/extension-accelerate"

const prisma = new PrismaClient().$extends(withAccelerate())

// クエリキャッシュ
const popular = await prisma.post.findMany({
  where: { published: true },
  cacheStrategy: { ttl: 60, swr: 300 },
})

Driver Adapters(Edge 対応)

Cloudflare Workers / Deno / Bun などのNode API が無い環境で プロバイダ固有の HTTP ドライバを使う仕組み。

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["driverAdapters"]
}
// Neon
import { neon } from "@neondatabase/serverless"
import { PrismaNeonHTTP } from "@prisma/adapter-neon"
import { PrismaClient } from "@prisma/client"

const adapter = new PrismaNeonHTTP(neon(process.env.DATABASE_URL!))
const prisma = new PrismaClient({ adapter })

他に PrismaPlanetScale, PrismaD1(Cloudflare D1), PrismaPg, PrismaLibSQL など。

Read Replica(読み取り専用 DB)

npm install @prisma/extension-read-replicas
import { PrismaClient } from "@prisma/client"
import { readReplicas } from "@prisma/extension-read-replicas"

const prisma = new PrismaClient().$extends(
  readReplicas({ url: process.env.DATABASE_REPLICA_URL! })
)

// 自動的に SELECT は replica へ
await prisma.post.findMany()

// 強制的に primary
await prisma.$primary().post.findMany()

ロギングとメトリクス

クエリログ

const prisma = new PrismaClient({
  log: [
    { emit: "event", level: "query" },
    { emit: "event", level: "warn" },
  ]
})

prisma.$on("query", (e) => {
  if (e.duration > 100) {
    console.warn(`SLOW QUERY ${e.duration}ms`, e.query, e.params)
  }
})

メトリクス(Prometheus)

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["metrics"]
}
const m = await prisma.$metrics.prometheus()
// /metrics エンドポイントで配信

Tracing(OpenTelemetry)

generator client {
  previewFeatures = ["tracing"]
}

JSON Schema 整合(Zod 連携)

外部入力を Zod で検証 → Prisma に渡すパターン。zod-prisma-types 等のジェネレータで自動化。

import { z } from "zod"

const NewPost = z.object({
  title: z.string().min(1).max(200),
  content: z.string().nullable(),
})

const data = NewPost.parse(input)
await prisma.post.create({ data })

大量データの処理

chunk 化バッチ

async function batchCreate(items: any[], chunk = 1000) {
  for (let i = 0; i < items.length; i += chunk) {
    await prisma.post.createMany({
      data: items.slice(i, i + chunk),
      skipDuplicates: true,
    })
  }
}

cursor stream

let cursor: string | undefined
while (true) {
  const batch = await prisma.post.findMany({
    take: 1000,
    ...(cursor && { skip: 1, cursor: { id: cursor } }),
    orderBy: { id: "asc" },
  })
  if (batch.length === 0) break
  for (const post of batch) {
    // 処理
  }
  cursor = batch[batch.length - 1].id
}

カスタムタイプ(Decimal / BigInt)

BigInt の JSON 化

// グローバル拡張
;(BigInt.prototype as any).toJSON = function () { return this.toString() }

// あるいは replacer
JSON.stringify(obj, (_, v) => typeof v === "bigint" ? v.toString() : v)

マルチスキーマ・マルチ DB

Prisma Postgres(公式マネージド)

Prisma 公式が出しているPostgres マネージド。Accelerate 込みで Edge から低遅延。 Free tier あり。Vercel 等から 1 クリックで連携。

Pulse(Realtime)

DB 変更イベントを TypeScript 側で購読する公式拡張(CDC)。

const stream = await prisma.post.stream({ name: "post-stream" })
for await (const event of stream) {
  // event.action: "create" | "update" | "delete"
  console.log(event)
}

Prisma Studio のアクセス制御

Studio は本番でそのまま公開しない。社内ツール経由 / VPN / SSH トンネル越しに使う。

v6 / 新 Client(TypeScript エンジン)

失敗パターン

症状対処
$queryRaw で型エラー明示的に型を指定(<Row[]>
SQL Injectionテンプレートリテラルか Prisma.sql を使う
Edge で動かないDriver Adapter / Accelerate を導入
巨大バンドルoutput dir を node_modules 外に / dynamic import
$extends 後の型推論新しいインスタンスを export し直す
使うべき機能、後回しにする機能

まず標準 API + $queryRaw + 楽観ロックで大半のニーズは満たせる。 $extends / Accelerate / Pulse は必要になってから導入する。早すぎる最適化は禁物。