跳到主要內容

如何在 Next.js 中使用 Prisma ORM

簡介

本指南將說明如何在 Next.js 15(一個全端 React 框架)中使用 Prisma ORM。您將學習如何設定 Prisma ORM 與 Next.js、處理遷移,以及將應用程式部署到 Vercel。您可以在 GitHub 上找到可供部署的範例

先決條件

在開始本指南之前,請確認您已具備:

  • 已安裝 Node.js 18+
  • Prisma Postgres 資料庫(或任何 PostgreSQL 資料庫)
  • 一個 Vercel 帳戶(如果您想部署應用程式)

1. 設定您的專案

從您要建立專案的目錄中,執行 create-next-app 來建立一個新的 Next.js 應用程式,我們將在本指南中使用它。

npx create-next-app@latest my-app --yes

然後,導覽至專案目錄

cd my-app

2. 設定 Prisma ORM

2.1 安裝 Prisma ORM 並建立您的第一個模型

首先,我們需要安裝一些相依性。在您終端機中的專案根目錄,執行

npm install prisma --save-dev
npm install tsx --save-dev
npm install @prisma/extension-accelerate
資訊

如果您未使用 Prisma Postgres 資料庫,則不需要 @prisma/extension-accelerate 套件。

然後,執行 prisma init 以在您的專案中初始化 Prisma ORM。

npx prisma init

這會在您的專案中建立一個新的 prisma 目錄,其中包含一個 schema.prisma 檔案。 schema.prisma 檔案是您定義資料庫模型的位置。

prisma init 命令也會在您的專案根目錄中建立一個 .env 檔案。此檔案用於儲存您的資料庫連線字串。

接下來,讓我們在您的 schema.prisma 檔案中新增兩個模型。一個 User 模型和一個 Post 模型。

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

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

這代表一個簡單的部落格,其中包含使用者和文章。每個 Post 可以有一個 User 作為作者,而每個 User 可以有多個 Post

現在我們有了一個 Prisma Schema 和一個模型,讓我們連線到我們的 Prisma Postgres 資料庫。

2.2 儲存您的資料庫連線字串

現在您有了 Prisma schema,您需要一個資料庫來應用您的 schema。

我還沒有資料庫

如果您還沒有資料庫,您可以在中建立一個新的資料庫。我們的開始使用指南中提供了逐步說明。

當您建立 Prisma Postgres 專案時,您會獲得一個 DATABASE_URLPULSE_API_KEY。將這些儲存在您的 .env 檔案中。

.env
DATABASE_URL=[Your Database URL Here]
PULSE_API_KEY=[Your Pulse API Key Here]
資訊

如果您未使用 Prisma Postgres,則不需要此處的 PULSE_API_KEY

2.3 更新您的資料庫架構

警告

如果您連線到具有現有資料的資料庫,請使用 prisma db pull 命令,然後跳到設定 Prisma Client

現在您已儲存您的資料庫連線字串,我們可以使用 prisma migrate dev 命令將您的 schema 應用到您的資料庫。

npx prisma migrate dev --name init

這會建立一個初始遷移,以建立 UserPost 資料表,並將該遷移應用到您的資料庫。

現在,讓我們為資料庫新增一些初始資料。

2.4 植入您的資料庫

Prisma ORM 內建支援使用初始資料植入您的資料庫。若要執行此操作,您可以在 prisma 目錄中建立一個名為 seed.ts 的新檔案。

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

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()

現在,將 prisma.seed 組態新增至您的 package.json 檔案。

package.json
{
"name": "my-app",
"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.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 db seed 以使用我們在 seed.ts 檔案中定義的初始資料來植入您的資料庫。

npx prisma db seed

我們現在有一個包含一些初始資料的資料庫!您可以透過執行 prisma studio 來查看資料庫中的資料。

npx prisma studio

2.5 設定 Prisma Client

現在我們有一個包含一些初始資料的資料庫,我們可以設定 Prisma Client 並將其連線到我們的資料庫。

在您的專案根目錄中,建立一個新的 lib 目錄,並向其中新增一個 prisma.ts 檔案。

mkdir -p lib && touch lib/prisma.ts

現在,將以下程式碼新增至您的 lib/prisma.ts 檔案

lib/prisma.ts
import { PrismaClient } from '@prisma/client'
import { withAccelerate } from '@prisma/extension-accelerate'

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

const globalForPrisma = global as unknown as { prisma: typeof prisma }

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

export default prisma

此檔案會建立一個 Prisma Client,並將其附加到全域物件,以便在您的應用程式中只會建立一個用戶端執行個體。這有助於解決在開發模式中將 Prisma ORM 與 Next.js 搭配使用時可能發生的熱重載問題。

注意

如果您未使用 Prisma Postgres,請將行

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

替換為

const prisma = new PrismaClient()

並移除 @prisma/extension-accelerate 匯入。

我們將在下一節中使用此用戶端來執行您的第一個查詢。

3. 使用 Prisma ORM 查詢您的資料庫

現在我們有了一個已初始化的 Prisma Client、一個與資料庫的連線以及一些初始資料,我們可以開始使用 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">
<li>My first post</li>
{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 Client 查詢中使用了 include 來擷取每篇文章的作者,以便我們可以顯示作者的姓名。

這個「清單檢視」是 Web 應用程式中最常見的模式之一。我們將在應用程式中新增另外兩個頁面,您也通常需要它們:一個「詳細檢視」和一個「建立檢視」。

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

這裡有很多變更,所以讓我們分解一下

  • 我們使用 Prisma Client 透過其 id 來獲取文章,而 id 是從 params 物件取得的。
  • 如果文章不存在(可能已被刪除或您輸入了錯誤的 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": "my-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"postinstall": "prisma generate",
"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"
}
}

在進行此變更之後,您可以執行 vercel 將您的應用程式部署到 Vercel。

vercel

在部署完成後,您可以造訪 Vercel 提供的 URL 來查看您的應用程式。恭喜,您剛剛使用 Prisma ORM 部署了一個 Next.js 應用程式!

8. 後續步驟

既然您已經有一個使用 Prisma ORM 的 Next.js 應用程式,以下是一些您可以擴展和改進應用程式的方式

  • 新增身分驗證以保護您的路由
  • 新增編輯和刪除文章的功能
  • 為文章新增評論
  • 使用 Prisma Studio 進行視覺化的資料庫管理

如需更多資訊和更新