Next.jsでPrisma ORMを使用する方法
はじめに
このガイドでは、フルスタックReactフレームワークであるNext.js 15でPrismaを使用する方法を説明します。Prisma Postgresインスタンスの作成、Next.jsでのPrisma ORMのセットアップ、マイグレーションの処理、アプリケーションのVercelへのデプロイについて学習します。
デプロイ可能なサンプルはGitHubで見つけることができます。
前提条件
- Node.js 18+
- Vercelアカウント (アプリケーションをデプロイしたい場合)
1. プロジェクトをセットアップする
プロジェクトを作成したいディレクトリから、create-next-app
を実行して、このガイドで使用する新しいNext.jsアプリを作成します。
npx create-next-app@latest nextjs-prisma
プロジェクトに関するいくつかの質問に答えるよう求められます。すべてのデフォルトを選択してください。
参考として、それらは以下の通りです。
- TypeScript
- ESLint
- Tailwind CSS
src
ディレクトリなし- App Router
- Turbopack
- カスタムインポートエイリアスなし
次に、プロジェクトディレクトリに移動します。
cd nextjs-prisma
2. Prismaのインストールと設定
2.1. 依存関係のインストール
Prismaを始めるには、いくつかの依存関係をインストールする必要があります。
- Prisma Postgres (推奨)
- その他のデータベース
npm install prisma tsx --save-dev
npm install @prisma/extension-accelerate @prisma/client
npm install prisma tsx --save-dev
npm install @prisma/client
インストール後、プロジェクトでPrismaを初期化します。
npx prisma init --db --output ../app/generated/prisma
Prisma Postgresデータベースをセットアップする際に、いくつかの質問に答える必要があります。お住まいの地域に最も近いリージョンと、「My __________ Project」のような覚えやすいデータベース名を選択してください。
これにより、以下のものが作成されます。
prisma
ディレクトリとschema.prisma
ファイル。- Prisma Postgresデータベース。
- プロジェクトルートに
DATABASE_URL
を含む.env
ファイル。 - 生成されたPrismaクライアント用の
output
ディレクトリ(app/generated/prisma
として)。
2.2. Prismaスキーマの定義
prisma/schema.prisma
ファイルに、以下のモデルを追加します。
generator client {
provider = "prisma-client-js"
output = "../app/generated/prisma"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id])
}
これにより、User
とPost
の2つのモデルが作成され、それらの間に1対多のリレーションシップが設定されます。
2.3. Prisma Clientジェネレータの設定
次に、以下のコマンドを実行してデータベーステーブルを作成し、Prisma Clientを生成します。
npx prisma migrate dev --name init
2.4. データベースのシード
サンプルユーザーと投稿でデータベースを埋めるために、シードデータを追加します。
prisma/
ディレクトリにseed.ts
という新しいファイルを作成します。
import { PrismaClient, Prisma } from "../app/generated/prisma";
const prisma = new PrismaClient();
const userData: Prisma.UserCreateInput[] = [
{
name: "Alice",
email: "alice@prisma.io",
posts: {
create: [
{
title: "Join the Prisma Discord",
content: "https://pris.ly/discord",
published: true,
},
{
title: "Prisma on YouTube",
content: "https://pris.ly/youtube",
},
],
},
},
{
name: "Bob",
email: "bob@prisma.io",
posts: {
create: [
{
title: "Follow Prisma on Twitter",
content: "https://www.twitter.com/prisma",
published: true,
},
],
},
},
];
export async function main() {
for (const u of userData) {
await prisma.user.create({ data: u });
}
}
main();
次に、package.json
を更新して、このスクリプトをPrismaで実行する方法を伝えます。
{
"name": "nextjs-prisma",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"prisma": {
"seed": "tsx prisma/seed.ts"
},
"dependencies": {
"@prisma/client": "^6.7.0",
"@prisma/extension-accelerate": "^1.3.0",
"next": "15.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.3.1",
"prisma": "^6.7.0",
"tailwindcss": "^4",
"tsx": "^4.19.4",
"typescript": "^5"
}
}
開発サーバーを起動する前に、Next.js v15.2.0またはv15.2.1を使用している場合、既知の問題があるため、Turbopackを使用しないでください。package.json
を更新して、開発スクリプトからTurbopackを削除してください。
"script":{
"dev": "next dev --turbopack",
"dev": "next dev",
}
この変更は、それ以前または以降のバージョンでは必要ありません。
最後に、prisma db seed
を実行して、seed.ts
ファイルで定義した初期データでデータベースをシードします。
シードスクリプトを実行する
npx prisma db seed
Prisma Studioを開いてデータを確認します
npx prisma studio
2.5. Prismaクライアントのセットアップ
これで初期データを含むデータベースができたので、Prismaクライアントをセットアップし、データベースに接続できます。
プロジェクトのルートに、新しいlib
ディレクトリを作成し、その中にprisma.ts
ファイルを追加します。
mkdir -p lib && touch lib/prisma.ts
次に、lib/prisma.ts
ファイルに以下のコードを追加します。
- Prisma Postgres (推奨)
- その他のデータベース
import { PrismaClient } from '../app/generated/prisma'
import { withAccelerate } from '@prisma/extension-accelerate'
const globalForPrisma = global as unknown as {
prisma: PrismaClient
}
const prisma = globalForPrisma.prisma || new PrismaClient().$extends(withAccelerate())
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
export default prisma
import { PrismaClient } from '../src/app/generated/prisma'
const globalForPrisma = global as unknown as {
prisma: PrismaClient
}
const prisma = globalForPrisma.prisma || new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
export default prisma
このファイルはPrismaクライアントを作成し、それをグローバルオブジェクトにアタッチします。これにより、アプリケーションでクライアントのインスタンスが1つだけ作成されます。これは、開発モードでPrisma ORMをNext.jsと組み合わせて使用する際に発生する可能性のあるホットリロードの問題を解決するのに役立ちます。
このクライアントは、次のセクションで最初のクエリを実行するために使用します。
3. Prisma ORMでデータベースをクエリする
これで、初期化されたPrismaクライアント、データベースへの接続、および初期データができたので、Prisma ORMでデータをクエリを開始できます。
この例では、アプリケーションの「ホーム」ページにすべてのユーザーを表示させます。
app/page.tsx
ファイルを開き、既存のコードを以下に置き換えます。
export default async function Home() {
return (
<div className="min-h-screen bg-gray-50 flex flex-col items-center justify-center -mt-16">
<h1 className="text-4xl font-bold mb-8 font-[family-name:var(--font-geist-sans)] text-[#333333]">
Superblog
</h1>
<ol className="list-decimal list-inside font-[family-name:var(--font-geist-sans)]">
<li className="mb-2">Alice</li>
<li>Bob</li>
</ol>
</div>
);
}
これにより、タイトルとユーザーのリストを持つ基本的なページが作成されます。ただし、このリストはハードコードされた静的な値です。データベースからユーザーを取得し、動的に表示するようにページを更新しましょう。
import prisma from '@/lib/prisma'
export default async function Home() {
const users = await prisma.user.findMany();
return (
<div className="min-h-screen bg-gray-50 flex flex-col items-center justify-center -mt-16">
<h1 className="text-4xl font-bold mb-8 font-[family-name:var(--font-geist-sans)] text-[#333333]">
Superblog
</h1>
<ol className="list-decimal list-inside font-[family-name:var(--font-geist-sans)]">
{users.map((user) => (
<li key={user.id} className="mb-2">
{user.name}
</li>
))}
</ol>
</div>
);
}
これでクライアントをインポートし、User
モデルからすべてのユーザーをクエリし、それらをリストに表示するようになりました。
これでホームページは動的になり、データベースからユーザーを表示するようになります。
3.1. データの更新(オプション)
データが更新されたときに何が起こるかを確認したい場合は、以下を行うことができます。
- 選択したSQLブラウザを介して
User
テーブルを更新する - より多くのユーザーを追加するように
seed.ts
ファイルを変更する prisma.user.findMany
の呼び出しを変更して、ユーザーを並べ替えたり、フィルターしたりする。
ページをリロードするだけで変更が反映されます。
4. 新しい投稿リストページを追加する
ホームページは機能していますが、すべての投稿を表示する新しいページを追加する必要があります。
まず、app
ディレクトリに新しいposts
ディレクトリを作成し、その中に新しいpage.tsx
ファイルを作成します。
mkdir -p app/posts && touch app/posts/page.tsx
次に、app/posts/page.tsx
ファイルに以下のコードを追加します。
import prisma from "@/lib/prisma";
export default async function Posts() {
return (
<div className="min-h-screen bg-gray-50 flex flex-col items-center justify-center -mt-16">
<h1 className="text-4xl font-bold mb-8 font-[family-name:var(--font-geist-sans)] text-[#333333]">
Posts
</h1>
<ul className="font-[family-name:var(--font-geist-sans)] max-w-2xl space-y-4">
<li>My first post</li>
</ul>
</div>
);
}
これでlocalhost:3000/posts
が読み込まれますが、コンテンツは再びハードコードされています。ホームページと同様に、動的に更新しましょう。
import prisma from "@/lib/prisma";
export default async function Posts() {
const posts = await prisma.post.findMany({
include: {
author: true,
},
});
return (
<div className="min-h-screen bg-gray-50 flex flex-col items-center justify-center -mt-16">
<h1 className="text-4xl font-bold mb-8 font-[family-name:var(--font-geist-sans)] text-[#333333]">
Posts
</h1>
<ul className="font-[family-name:var(--font-geist-sans)] max-w-2xl space-y-4">
{posts.map((post) => (
<li key={post.id}>
<span className="font-semibold">{post.title}</span>
<span className="text-sm text-gray-600 ml-2">
by {post.author.name}
</span>
</li>
))}
</ul>
</div>
);
}
これはホームページと似ていますが、ユーザーを表示する代わりに投稿を表示します。また、Prismaクライアントクエリでinclude
を使用して、各投稿の作成者を取得し、作成者の名前を表示できるようにしていることがわかります。
この「リストビュー」は、Webアプリケーションで最も一般的なパターンの一つです。アプリケーションにさらに2つのページを追加します。これらも一般的に必要となる「詳細ビュー」と「作成ビュー」です。
5. 新しい投稿詳細ページを追加する
投稿リストページを補完するために、投稿詳細ページを追加します。
posts
ディレクトリ内に、新しい[id]
ディレクトリと、その中に新しいpage.tsx
ファイルを作成します。
mkdir -p app/posts/[id] && touch app/posts/[id]/page.tsx
このページには、単一の投稿のタイトル、コンテンツ、作成者が表示されます。他のページと同様に、app/posts/new/page.tsx
ファイルに以下のコードを追加します。
import prisma from "@/lib/prisma";
export default async function Post({ params }: { params: Promise<{ id: string }> }) {
return (
<div className="min-h-screen bg-gray-50 flex flex-col items-center justify-center -mt-16">
<article className="max-w-2xl space-y-4 font-[family-name:var(--font-geist-sans)]">
<h1 className="text-4xl font-bold mb-8 text-[#333333]">My first post</h1>
<p className="text-gray-600 text-center">by Anonymous</p>
<div className="prose prose-gray mt-8">
No content available.
</div>
</article>
</div>
);
}
以前と同様に、このページはハードコードされた静的コンテンツです。ページのparams
に基づいて動的に更新しましょう。
import prisma from "@/lib/prisma";
import { notFound } from "next/navigation";
export default async function Post({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const post = await prisma.post.findUnique({
where: { id: parseInt(id) },
include: {
author: true,
},
});
if (!post) {
notFound();
}
return (
<div className="min-h-screen bg-gray-50 flex flex-col items-center justify-center -mt-16">
<article className="max-w-2xl space-y-4 font-[family-name:var(--font-geist-sans)]">
<h1 className="text-4xl font-bold mb-8 text-[#333333]">{post.title}</h1>
<p className="text-gray-600 text-center">by {post.author.name}</p>
<div className="prose prose-gray mt-8">
{post.content || "No content available."}
</div>
</article>
</div>
);
}
ここには多くの変更があるので、分解してみましょう。
params
オブジェクトから取得したid
を使って、Prisma Clientで投稿を取得しています。- 投稿が存在しない場合(削除されたか、IDが間違っていたか)、
notFound()
を呼び出して404ページを表示します。 - 次に、投稿のタイトル、コンテンツ、および作成者を表示します。投稿にコンテンツがない場合は、プレースホルダーメッセージを表示します。
最も美しいページではありませんが、良いスタートです。localhost:3000/posts/1
とlocalhost:3000/posts/2
に移動して試してみてください。また、localhost:3000/posts/999
に移動して404ページもテストできます。
6. 新しい投稿作成ページを追加する
アプリケーションを完成させるために、投稿の「作成」ページを追加します。これにより、独自の投稿を書き込み、データベースに保存できるようになります。
他のページと同様に、まず静的ページを作成し、次に動的に更新します。
mkdir -p app/posts/new && touch app/posts/new/page.tsx
次に、app/posts/new/page.tsx
ファイルに以下のコードを追加します。
import Form from "next/form";
export default function NewPost() {
async function createPost(formData: FormData) {
"use server";
const title = formData.get("title") as string;
const content = formData.get("content") as string;
}
return (
<div className="max-w-2xl mx-auto p-4">
<h1 className="text-2xl font-bold mb-6">Create New Post</h1>
<Form action={createPost} className="space-y-6">
<div>
<label htmlFor="title" className="block text-lg mb-2">
Title
</label>
<input
type="text"
id="title"
name="title"
placeholder="Enter your post title"
className="w-full px-4 py-2 border rounded-lg"
/>
</div>
<div>
<label htmlFor="content" className="block text-lg mb-2">
Content
</label>
<textarea
id="content"
name="content"
placeholder="Write your post content here..."
rows={6}
className="w-full px-4 py-2 border rounded-lg"
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-3 rounded-lg hover:bg-blue-600"
>
Create Post
</button>
</Form>
</div>
);
}
このフォームは見た目は良いですが、まだ何も機能しません。createPost
関数を更新して、投稿をデータベースに保存しましょう。
import Form from "next/form";
import prisma from "@/lib/prisma";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
export default function NewPost() {
async function createPost(formData: FormData) {
"use server";
const title = formData.get("title") as string;
const content = formData.get("content") as string;
await prisma.post.create({
data: {
title,
content,
authorId: 1,
},
});
revalidatePath("/posts");
redirect("/posts");
}
return (
<div className="max-w-2xl mx-auto p-4">
<h1 className="text-2xl font-bold mb-6">Create New Post</h1>
<Form action={createPost} className="space-y-6">
<div>
<label htmlFor="title" className="block text-lg mb-2">
Title
</label>
<input
type="text"
id="title"
name="title"
placeholder="Enter your post title"
className="w-full px-4 py-2 border rounded-lg"
/>
</div>
<div>
<label htmlFor="content" className="block text-lg mb-2">
Content
</label>
<textarea
id="content"
name="content"
placeholder="Write your post content here..."
rows={6}
className="w-full px-4 py-2 border rounded-lg"
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-3 rounded-lg hover:bg-blue-600"
>
Create Post
</button>
</Form>
</div>
);
}
これでこのページには機能するフォームができました!フォームを送信すると、新しい投稿がデータベースに作成され、投稿リストページにリダイレクトされます。
また、投稿リストページを再検証するためにrevalidatePath
呼び出しを追加し、新しい投稿で更新されるようにしました。これにより、誰もがすぐに新しい投稿を読むことができます。
localhost:3000/posts/new
にアクセスしてフォームを送信してみてください。
7. アプリケーションをVercelにデプロイする(オプション)
アプリケーションをVercelにデプロイする最も手っ取り早い方法は、Vercel CLIを使用することです。
まず、Vercel CLIをインストールします。
npm install -g vercel
次に、vercel login
を実行してVercelアカウントにログインします。
vercel login
デプロイする前に、VercelにPrisma Clientが生成されることを確認するよう伝える必要があります。これは、package.json
ファイルにpostinstall
スクリプトを追加することで行えます。
{
"name": "nextjs-prisma",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"postinstall": "prisma generate --no-engine",
"start": "next start",
"lint": "next lint"
},
"prisma": {
"seed": "tsx prisma/seed.ts"
},
"dependencies": {
"@prisma/client": "^6.2.1",
"@prisma/extension-accelerate": "^1.2.1",
"next": "15.1.4",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.1.4",
"postcss": "^8",
"prisma": "^6.2.1",
"tailwindcss": "^3.4.1",
"tsx": "^4.19.2",
"typescript": "^5"
}
}
Prisma Postgresを使用していない場合は、コマンドから--no-engine
フラグを削除する必要があります。
この変更後、vercel
を実行してアプリケーションをVercelにデプロイできます。
vercel
デプロイが完了すると、Vercelが提供するURLでアプリケーションにアクセスできます。おめでとうございます、Next.jsアプリケーションをPrisma ORMでデプロイしました!
8. 次のステップ
Prisma ORMを搭載したNext.jsアプリケーションが動作するようになったので、アプリケーションを拡張・改善するためのいくつかの方法を以下に示します。
- ルートを保護するための認証を追加する
- 投稿の編集・削除機能を追加する
- 投稿にコメントを追加する
- 視覚的なデータベース管理にはPrisma Studioを使用する
詳細情報
Prismaとつながり続ける
以下の方法でPrismaの旅を続けましょう 私たちの活発なコミュニティに参加しましょう。情報収集、参加、他の開発者との協力
- Xでフォローする アナウンス、ライブイベント、役立つヒントについて。
- Discordに参加する 質問したり、コミュニティと話したり、会話を通してアクティブなサポートを受けたりできます。
- YouTubeを購読する チュートリアル、デモ、ストリームについて。
- GitHubで交流する リポジトリにスターを付けたり、問題を報告したり、問題に貢献したりしてください。