Next.js + Prisma 完全ガイド:DB接続の落とし穴から本番運用まで

「SQL を書きたくない、でも NoSQL だとリレーションが辛い」
そんなフロントエンド出身のフルスタックエンジニアにとって、Prisma は救世主のような存在です。型安全性、直感的な API、そして見やすいスキーマ定義ファイル。一度使ったら、もう生の SQL 文字列を組み立てる作業には戻れません。
しかし、Next.js と Prisma を組み合わせる際には、いくつか落とし穴があります。
最も有名なのが、開発中に「Too many connections(接続数が多すぎます)」というエラーが出てデータベースが死ぬ現象です。これは Next.js のホットリロードの仕組みと関係しています。
この記事では、Prisma の導入から、この悪名高い接続問題の解決、リレーションの扱い、そしてパフォーマンスを意識したクエリの書き方まで、実務で必要な知識を一本にまとめました。
なぜ Prisma なのか?
Prisma は「次世代の ORM(Object-Relational Mapping)」と呼ばれています。従来の ORM との最大の違いは、型生成エンジンです。
schema.prisma というファイルを一つ書くだけで、そこからデータベースのマイグレーション用 SQL と、TypeScript の型定義ファイルの両方を生成してくれます。これにより、「DB のカラム名を変えたのに、コードの型定義を直し忘れて実行時エラー」という事故が物理的に起こらなくなります。
ステップ 1: セットアップとデータベース接続
まず、Next.js プロジェクトに Prisma をインストールします。
npm install prisma --save-dev
npm install @prisma/client
npx prisma initこれで prisma フォルダと .env ファイルが生成されます。.env の DATABASE_URL を自分のデータベース(PostgreSQL, MySQL など)に合わせて書き換えてください。
【重要】開発環境での接続リークを防ぐ
ここがこの記事で一番重要なポイントです。
何も考えずに const prisma = new PrismaClient() を書くと、Next.js でファイルを保存(ホットリロード)するたびに新しい接続プールが作られ、すぐに DB の最大接続数に達してエラーになります。
これを防ぐために、シングルトンインスタンスを作成するヘルパーファイルを作ります。
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
// global オブジェクトに prisma プロパティを追加して型拡張
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
}今後は、アプリ内のどこでもこの lib/prisma.ts から prisma をインポートして使ってください。これで開発中も接続数は1つに保たれます。
ステップ 2: スキーマ設計とリレーション
ブログシステムを例に、prisma/schema.prisma を定義してみましょう。
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[] // 1対多の関係(一人のユーザーは複数の記事を持つ)
profile Profile? // 1対1の関係
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id]) // 外部キー
tags Tag[] // 多対多の関係(暗黙の中間テーブルが作られる)
createdAt DateTime @default(now())
}
model Profile {
id Int @id @default(autoincrement())
bio String
userId Int @unique
user User @relation(fields: [userId], references: [id])
}
model Tag {
id Int @id @default(autoincrement())
name String
posts Post[]
}書き終わったら、マイグレーションを実行して DB に反映させます。
npx prisma migrate dev --name initこれで DB にテーブルが作成され、同時に @prisma/client の型定義も更新されます。
ステップ 3: CRUD 操作の実践
Prisma のクエリは直感的です。lib/prisma.ts をインポートして使います。
データの作成(Create)
リレーションがあるデータもまとめて作れます(ネストされた書き込み)。
const user = await prisma.user.create({
data: {
email: '[email protected]',
name: 'Alice',
posts: {
create: [
{ title: '初めてのブログ', content: 'こんにちは!' },
{ title: 'Prismaの使い心地', published: true },
],
},
},
})データの読み取り(Read)
include を使うと、関連するテーブルのデータも一度に取得できます(JOIN)。
// 全記事を取得し、著者の情報も含める
const posts = await prisma.post.findMany({
where: {
published: true,
},
include: {
author: {
select: { name: true }, // authorから名前だけを取得
},
tags: true,
},
})select を使うと、特定のフィールドだけを取得して転送量を減らせます。include と select は同じ階層では併用できないので注意が必要です(ネストすればOK)。
データの更新(Update)
const updatedPost = await prisma.post.update({
where: { id: 1 },
data: {
published: true,
viewCount: {
increment: 1, // 現在の値 + 1
},
},
})トランザクション処理
複数の処理を「全部成功するか、全部失敗するか」にしたい場合は $transaction を使います。
const [user, post] = await prisma.$transaction([
prisma.user.create({ data: { email: '[email protected]' } }),
prisma.post.create({ data: { title: 'Bobの記事' } }), // authorId不足で失敗すると、上のuser作成もロールバックされる
])パフォーマンス・ベストプラクティス
1. N+1 問題を避ける
やってはいけない例:
const users = await prisma.user.findMany();
for (const user of users) {
// ループの中でクエリを投げている(最悪!)
const posts = await prisma.post.findMany({ where: { authorId: user.id } });
}正しい例:
const users = await prisma.user.findMany({
include: { posts: true }, // 一回のクエリで取得
});2. インデックスを貼る
検索によく使うカラムにはインデックスを貼りましょう。
model User {
// ...
email String @unique
@@index([email]) // インデックス追加
}3. 不要なデータは取得しない
findMany() はデフォルトですべてのカラムを取得します(SELECT *)。巨大なテキストデータなどがある場合は、select で必要なカラムだけを指定しましょう。
まとめ
Prisma は Next.js 開発におけるデータ層の最良のパートナーです。
- 型安全性: 入力補完が効き、typo を撲滅できる。
- 生産性: リレーションやマイグレーションが直感的。
- 安心感: シングルトンパターンで接続リークも怖くない。
最初は少し独自の記法(schema.prisma)を覚える必要がありますが、その投資効果は絶大です。
Next.js + Prisma 導入フロー
インストールから接続設定、開発環境での接続リーク対策までの手順
⏱️ Estimated time: 15 min
- 1
Step1: インストールと初期化
npm install prisma @prisma/client を実行し、npx prisma init で設定ファイルを生成します。 - 2
Step2: 環境変数の設定
.env ファイルの DATABASE_URL を実際のデータベース接続文字列に変更します。 - 3
Step3: Prisma クライアントのシングルトン化
lib/prisma.ts を作成し、globalThis を使って開発環境でクライアントインスタンスを再利用するコードを記述します。これが接続リーク対策になります。 - 4
Step4: スキーマの定義
prisma/schema.prisma にモデル(テーブル)定義を記述します。 - 5
Step5: マイグレーション実行
npx prisma migrate dev --name init を実行し、DB にテーブルを作成して型定義を生成します。
FAQ
開発中に 'Too many connections' エラーが出ます。
Prisma は本番環境でも使えますか?パフォーマンスは?
マイグレーションファイルは Git に含めるべきですか?
Vercel などのサーバーレス環境での注意点は?
3 min read · 公開日: 2025年12月20日 · 更新日: 2026年1月22日
関連記事
Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践

Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践
Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド

Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド
Next.js ユニットテスト実践:Jest + React Testing Library 完全設定ガイド


コメント
GitHubアカウントでログインしてコメントできます