•7 min read

Next.js App RouterとPrisma: セットアップとコネクションプーリング

Next.js App RouterとPrisma: セットアップとコネクションプーリング
Next.js App Router and Prisma Architecture Guide
Audio Briefing
0:00 / 0:00
Part of a Series

モダンNext.jsアーキテクチャシリーズ

Part 3 of 3

Next.js App Routerが導入されたとき、それはフルスタックReact開発における根本的なパラダイムシフトを意味しました。React Server Components(RSC)とPrisma ORMを組み合わせることで、開発者はサーバーコンポーネントから直接データベースをクエリでき、クライアント側のフェッチウォーターフォールをゼロにし、完全なTypeScriptの安全性と、楽なServer Actionミューテーションを実現します。

この包括的なガイドでは、コネクションプーリング、プール枯渇を防ぐためのグローバルシングルトン、型安全なServer Actions、自動化されたデータベースシードスクリプトなど、本番環境に対応したNext.jsとPrismaアプリケーションを構築する方法を探ります。


1. プロジェクトの初期化とPrismaスキーマ

Prismaをインストールし、データベース接続を初期化します。

npm install prisma @prisma/client
npx prisma init

prisma/schema.prisma内にリレーショナルモデルを定義します。

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        String   @id @default(cuid())
  title     String
  content   String?
  published Boolean  @default(false)
  authorId  String
  author    User     @relation(fields: [authorId], references: [id])
  createdAt DateTime @default(now())
}

Advertisement

2. コネクションプールの枯渇防止(グローバルシングルトン)

ローカル開発中、Next.js Fast Refreshはファイル編集ごとにNode.jsモジュールキャッシュをクリアします。個々のルートファイル内でnew PrismaClient()をインスタンス化すると、ホットリロードのたびに新しいデータベース接続が作成され、すぐにPostgreSQLのmax_connectionsエラーが発生します。

lib/prisma.tsに堅牢なシングルトンを作成します。

// lib/prisma.ts
import { PrismaClient } from '@prisma/client'

const prismaClientSingleton = () => {
  return new PrismaClient({
    log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
  })
}

declare global {
  var prismaGlobal: undefined | ReturnType<typeof prismaClientSingleton>
}

const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()

export default prisma

if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma

3. React Server Componentsでの直接データフェッチ

Server Componentsはサーバー上でのみ実行されるため、接続文字列を公開したり、中間APIルートを構築したりすることなく、Prismaを直接クエリできます。

// app/posts/page.tsx
import prisma from '@/lib/prisma'
import Link from 'next/link'

export const revalidate = 3600 // Cache page for 1 hour

export default async function PostsPage() {
  const posts = await prisma.post.findMany({
    where: { published: true },
    include: { author: { select: { name: true, email: true } } },
    orderBy: { createdAt: 'desc' },
  })

  return (
    <main className="max-w-4xl mx-auto p-8">
      <h1 className="text-3xl font-bold mb-6">Published Articles</h1>
      <div className="grid gap-4">
        {posts.map((post) => (
          <article key={post.id} className="p-5 border rounded-xl shadow-sm">
            <h2 className="text-xl font-semibold">{post.title}</h2>
            <p className="text-sm text-gray-500">By {post.author.name}</p>
          </article>
        ))}
      </div>
    </main>
  )
}

4. Server Actionsによる型安全なデータミューテーション

データをミューテーションするには、'use server'でServer Actionsを宣言し、キャッシュタグを再検証します。

// app/actions/createPost.ts
'use server'

import prisma from '@/lib/prisma'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  const content = formData.get('content') as string
  const authorId = formData.get('authorId') as string

  if (!title || !authorId) {
    throw new Error('Title and Author are required')
  }

  const post = await prisma.post.create({
    data: { title, content, published: true, authorId },
  })

  revalidatePath('/posts')
  redirect(`/posts/${post.id}`)
}

Advertisement

5. データベースの自動シード

開発データベースをシードするには、prisma/seed.tsを作成します。

// prisma/seed.ts
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function main() {
  await prisma.post.deleteMany()
  await prisma.user.deleteMany()

  const user = await prisma.user.create({
    data: {
      email: 'admin@locionic.com',
      name: 'Locionic',
      posts: {
        create: [
          { title: 'Next.js App Router Architecture', published: true },
          { title: 'Prisma Connection Pooling Guide', published: true },
        ],
      },
    },
  })
  console.log('Database seeded successfully:', user.email)
}

main()
  .catch((e) => {
    console.error(e)
    process.exit(1)
  })
  .finally(async () => {
    await prisma.$disconnect()
  })

package.jsonにシードスクリプトの設定を追加します。

"prisma": {
  "seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
}

npx prisma db seedでシードコマンドを実行します。

Next.js Fast Refreshはファイル変更ごとにモジュールを再構築し、ホットリロードのたびに新しいPrismaClientインスタンスを作成します。クライアントをNode.jsのグローバルオブジェクト(globalThis.prismaGlobal)にアタッチすることで、開発サーバーのライフサイクル中に単一の接続プールインスタンスのみが存在するようにします。

いいえ、Prismaはブラウザのランタイム内では実行できない直接的なTCPデータベースソケット接続を必要とします。Prismaのクエリは、Server Components、Route Handlers、またはServer Actions内でのみ実行してください。

VercelやAWS Lambdaのようなサーバーレスプラットフォームにデプロイする場合、PgBouncer、Neon Serverless Pooling、Supabase Supavisor、またはPrisma Accelerateと?pgbouncer=true&connection_limit=1のようなコネクションプーラーを介して接続します。

package.jsonにprisma/seed.tsを指すprisma.seedコマンドを定義し、npx prisma db seedを実行してデモデータを決定論的に投入します。


こちらもおすすめ

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement