如何搭配 Next.js 使用 Prisma ORM
簡介
本指南將說明如何搭配 Next.js 15 這個全端 React 框架使用 Prisma ORM。您將學習如何使用 Next.js 設定 Prisma ORM、處理遷移,以及將您的應用程式部署到 Vercel。您可以在 GitHub 上找到可立即部署的範例。
先決條件
開始本指南之前,請確保您已具備下列條件:
- 已安裝 Node.js 18+
- Prisma Postgres 資料庫 (或任何 PostgreSQL 資料庫)
- Vercel 帳戶 (如果您想要部署您的應用程式)
1. 設定您的專案
從您想要建立專案的目錄中,執行 create-next-app
以建立新的 Next.js 應用程式,我們將在本指南中使用它。
npx create-next-app@latest my-app
系統會提示您回答幾個關於專案的問題。請選取所有預設值。
為了完整性,這些預設值為:
- TypeScript
- ESLint
- Tailwind CSS
- 無
src
目錄 - App Router
- Turbopack
- 無自訂匯入別名
然後,導覽至專案目錄
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
模型。
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_URL
。將其儲存在您的 .env
檔案中。
DATABASE_URL="prisma+postgres://accelerate.prisma-data.net/?api_key=eyJhbGciOiJIU...""
2.3 更新您的資料庫 schema
如果您要連線到具有現有資料的資料庫,請使用 prisma db pull
命令,然後跳到設定 Prisma Client。
現在您已儲存資料庫連線字串,我們可以使用 prisma migrate dev
命令將您的 schema 套用至您的資料庫。
npx prisma migrate dev --name init
這會建立初始遷移,建立 User
和 Post
表格,並將該遷移套用至您的資料庫。
現在,讓我們將一些初始資料新增至我們的資料庫。
2.4 植入您的資料庫
Prisma ORM 內建支援使用初始資料植入您的資料庫。若要執行此操作,您可以在 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
檔案。
{
"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
檔案
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
檔案,並將現有程式碼替換為下列程式碼
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">
<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
檔案
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]">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
擷取文章,我們從params
物件中取得該id
。 - 如果文章不存在 (可能是已刪除或您輸入了錯誤的 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。您可以透過將 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 進行視覺化資料庫管理
如需更多資訊和更新
- Prisma ORM 文件
- Prisma Client API 參考
- Next.js 文件
- 加入我們的 Discord 社群
- 在 Twitter 上關注我們