如何將 Prisma ORM 與 Next.js 搭配使用
簡介
本頁面解答的問題
- 如何設定 Prisma 與 Next.js?
- 如何將 Next.js 搭配 Prisma 部署到 Vercel?
- 如何在生產環境中處理遷移(Migrations)?
本指南將向您展示如何將 Prisma 搭配全端 React 框架 Next.js 15 一起使用。您將學習如何建立 Prisma Postgres 實例、設定 Next.js 的 Prisma ORM、處理遷移,以及將您的應用程式部署到 Vercel。
您可以在 GitHub 上找到一個可供部署的範例。
先決條件
- Node.js 20+
- 一個 Vercel 帳號(如果您想要部署您的應用程式)
1. 設定您的專案
在您想要建立專案的目錄中,執行 create-next-app 來建立一個新的 Next.js 應用程式,並在本指南中使用它。
npx create-next-app@latest nextjs-prisma
系統會提示您回答幾個關於專案的問題。請全部選擇預設值。
作為參考,這些選項為:
- TypeScript
- ESLint
- Tailwind CSS
- 不使用
src目錄 - App Router
- Turbopack
- 不使用自訂導入別名 (import alias)
接著,切換到專案目錄
cd nextjs-prisma
2. 安裝與設定 Prisma
2.1. 安裝依賴項目
要開始使用 Prisma,您需要安裝一些依賴項目
npm install prisma tsx @types/pg --save-dev
npm install @prisma/client @prisma/adapter-pg dotenv pg
如果您使用的是不同的資料庫提供者(MySQL、SQL Server、SQLite),請安裝相應的驅動程式適配器套件,而不是 @prisma/adapter-pg。如需更多資訊,請參閱資料庫驅動程式。
安裝完成後,在您的專案中初始化 Prisma
npx prisma init --db --output ../app/generated/prisma
在設定 Prisma Postgres 資料庫時,您需要回答幾個問題。請選擇最靠近您位置的區域,並為資料庫取一個好記的名字,例如「My __________ Project」。
這將建立:
- 一個包含
schema.prisma檔案的prisma目錄。 - 一個用於設定 Prisma 的
prisma.config.ts檔案 - 一個 Prisma Postgres 資料庫。
- 一個在專案根目錄下包含
DATABASE_URL的.env檔案。 - 為產生的 Prisma Client 設定一個
output目錄,例如app/generated/prisma。
2.2. 定義您的 Prisma Schema
在 prisma/schema.prisma 檔案中,新增以下模型
generator client {
provider = "prisma-client"
output = "../app/generated/prisma"
}
datasource db {
provider = "postgresql"
}
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.3 在 prisma.config.ts 加入 dotenv
要存取 .env 檔案中的變數,可以透過執行階段載入,或是使用 dotenv。在 prisma.config.ts 的頂部包含一個 dotenv 的匯入:
import 'dotenv/config'
import { defineConfig, env } from 'prisma/config';
export default defineConfig({
schema: 'prisma/schema.prisma',
migrations: {
path: 'prisma/migrations'
},
datasource: {
url: env('DATABASE_URL'),
},
});
2.4. 執行遷移並產生 Prisma Client
現在,執行下列指令來建立資料庫表:
npx prisma migrate dev --name init
接著產生 Prisma Client:
npx prisma generate
2.5. 進行資料庫植入 (Seed)
加入一些種子資料,以在資料庫中填充範例使用者與貼文。
在 prisma/ 目錄中建立一個名為 seed.ts 的新檔案
import { PrismaClient, Prisma } from "../app/generated/prisma/client";
import { PrismaPg } from '@prisma/adapter-pg'
import 'dotenv/config'
const adapter = new PrismaPg({
connectionString: process.env.DATABASE_URL,
})
const prisma = new PrismaClient({
adapter,
});
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.config.ts 來告訴 Prisma 如何執行此指令碼
import 'dotenv/config'
import { defineConfig, env } from 'prisma/config';
export default defineConfig({
schema: 'prisma/schema.prisma',
migrations: {
path: 'prisma/migrations',
seed: `tsx prisma/seed.ts`,
},
datasource: {
url: env('DATABASE_URL'),
},
});
在啟動開發伺服器之前,請注意如果您使用的是 Next.js v15.2.0 或 v15.2.1,請勿使用 Turbopack,因為存在一個已知的問題。請更新您的 package.json,從 dev 腳本中移除 Turbopack:
"script":{
"dev": "next dev --turbopack",
"dev": "next dev",
}
在此版本之前或之後的版本都不需要此變更。
最後,執行 prisma db seed,使用我們在 seed.ts 檔案中定義的初始資料來填充您的資料庫。
執行種子指令碼
npx prisma db seed
並開啟 Prisma Studio 來檢視您的資料
npx prisma studio
2.6 設定 Prisma Client
現在您已擁有一個包含初始資料的資料庫,您可以設定 Prisma Client 並將其連接到您的資料庫。
在您的專案根目錄中,建立一個新的 lib 目錄,並在其中加入一個 prisma.ts 檔案。
mkdir -p lib && touch lib/prisma.ts
現在,將下列程式碼加入到您的 lib/prisma.ts 檔案中:
import { PrismaClient } from '../app/generated/prisma/client'
import { PrismaPg } from '@prisma/adapter-pg'
const globalForPrisma = global as unknown as {
prisma: PrismaClient
}
const adapter = new PrismaPg({
connectionString: process.env.DATABASE_URL,
})
const prisma = globalForPrisma.prisma || new PrismaClient({
adapter,
})
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
export default prisma
此檔案會建立一個 Prisma Client 並將其掛載到全域物件上,確保您的應用程式中只會建立一個 Client 實例。這有助於解決在開發模式下將 Prisma ORM 搭配 Next.js 使用時,熱重載(Hot Reloading)可能引發的問題。
您將在下一節中使用此 Client 來執行您的第一個查詢。
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>
);
}
這會為您提供一個包含標題和使用者列表的基本頁面。然而,目前該列表是靜態且寫死(hardcoded)的。讓我們更新該頁面,從資料庫取得使用者,使其成為動態頁面。
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>
);
}
您現在已經導入了 Client,對 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 text-[#333333]">
<h1 className="text-4xl font-bold mb-8 font-[family-name:var(--font-geist-sans)]">
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 text-[#333333]">
<h1 className="text-4xl font-bold mb-8 font-[family-name:var(--font-geist-sans)]">
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 Client 查詢中使用了 include 來取得每篇文章的作者,以便顯示作者名稱。
這種「列表視圖」是網頁應用程式中最常見的模式之一。接下來您將為應用程式新增另外兩個常見頁面:「詳細視圖」和「建立視圖」。
5. 新增「文章詳細」頁面
為了完善「文章列表」頁面,您將新增一個文章詳細頁面。
在 posts 目錄中,建立一個新的 [id] 目錄,並在其中建立一個新的 page.tsx 檔案。
mkdir -p "app/posts/[id]" && touch "app/posts/[id]/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 將其更新為動態內容:
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>
);
}
這裡有很多變更,讓我們拆解一下:
- 您使用 Prisma Client 透過
id來取得文章,該id是從params物件中取得的。 - 如果文章不存在(可能是被刪除了,或是輸入了錯誤的 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": "nextjs-prisma",
"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/adapter-pg": "^6.2.1",
"@prisma/client": "^6.2.1",
"next": "15.1.4",
"pg": "^8.13.1",
"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. 後續步驟
現在您已經擁有一個運作良好的 Next.js 應用程式與 Prisma ORM,以下是一些您可以擴充和改善應用程式的方法:
- 新增驗證機制 (Authentication) 以保護您的路由
- 新增編輯和刪除文章的功能
- 新增文章留言功能
- 使用 Prisma Studio 進行視覺化資料庫管理
更多資訊
與 Prisma 保持聯繫
透過以下方式與我們聯繫,繼續您的 Prisma 旅程: 我們的活躍社群。保持資訊靈通、參與其中,並與其他開發者合作
- 在 X 上關注我們 以獲取公告、現場活動和實用技巧。
- 加入我們的 Discord 提出問題、與社群對話,並透過對話獲得積極支援。
- 在 YouTube 上訂閱 查看教學、演示和直播。
- 在 GitHub 上交流 透過為存放庫加星標、報告問題或為 issue 做出貢獻。