メインコンテンツへスキップ

Next.jsでPrisma ORMを使用する方法

20分

はじめに

このガイドでは、フルスタック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を始めるには、いくつかの依存関係をインストールする必要があります。

npm install prisma tsx --save-dev
npm install @prisma/extension-accelerate @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ファイルに、以下のモデルを追加します。

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])
}

これにより、UserPostの2つのモデルが作成され、それらの間に1対多のリレーションシップが設定されます。

2.3. Prisma Clientジェネレータの設定

次に、以下のコマンドを実行してデータベーステーブルを作成し、Prisma Clientを生成します。

npx prisma migrate dev --name init

2.4. データベースのシード

サンプルユーザーと投稿でデータベースを埋めるために、シードデータを追加します。

prisma/ディレクトリにseed.tsという新しいファイルを作成します。

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で実行する方法を伝えます。

package.json
{
"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を削除してください。

package.json
"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ファイルに以下のコードを追加します。

lib/prisma.ts
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

このファイルはPrismaクライアントを作成し、それをグローバルオブジェクトにアタッチします。これにより、アプリケーションでクライアントのインスタンスが1つだけ作成されます。これは、開発モードでPrisma ORMをNext.jsと組み合わせて使用する際に発生する可能性のあるホットリロードの問題を解決するのに役立ちます。

このクライアントは、次のセクションで最初のクエリを実行するために使用します。

3. Prisma ORMでデータベースをクエリする

これで、初期化されたPrismaクライアント、データベースへの接続、および初期データができたので、Prisma ORMでデータをクエリを開始できます。

この例では、アプリケーションの「ホーム」ページにすべてのユーザーを表示させます。

app/page.tsxファイルを開き、既存のコードを以下に置き換えます。

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>
);
}

これにより、タイトルとユーザーのリストを持つ基本的なページが作成されます。ただし、このリストはハードコードされた静的な値です。データベースからユーザーを取得し、動的に表示するようにページを更新しましょう。

app/page.tsx
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ファイルに以下のコードを追加します。

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が読み込まれますが、コンテンツは再びハードコードされています。ホームページと同様に、動的に更新しましょう。

app/posts/page.tsx
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ファイルに以下のコードを追加します。

app/posts/[id]/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に基づいて動的に更新しましょう。

app/posts/[id]/page.tsx
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/1localhost: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ファイルに以下のコードを追加します。

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関数を更新して、投稿をデータベースに保存しましょう。

app/posts/new/page.tsx
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スクリプトを追加することで行えます。

package.json
{
"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で交流する リポジトリにスターを付けたり、問題を報告したり、問題に貢献したりしてください。
皆様のご参加を心よりお待ちしております!

© . All rights reserved.