如何在 Next.js 中使用 Prisma ORM
介绍
¥Introduction
本指南将向你展示如何将 Prisma 与 Next.js 15(一个全栈 React 框架)结合使用。你将学习如何创建 Prisma Postgres 实例、使用 Next.js 设置 Prisma ORM、处理迁移以及将应用部署到 Vercel。
¥This guide shows you how to use Prisma with Next.js 15, a fullstack React framework. You'll learn how to create a Prisma Postgres instance, set up Prisma ORM with Next.js, handle migrations, and deploy your application to Vercel.
你可以找到 GitHub 上的部署就绪示例。
¥You can find a deployment-ready example on GitHub.
先决条件
¥Prerequisites
-
一个 Vercel 账户(如果你要部署应用)
¥A Vercel account (if you want to deploy your application)
1. 设置你的项目
¥ Set up your project
在你要创建项目的目录中,运行 create-next-app
来创建一个新的 Next.js 应用,你将在本指南中使用。
¥From the directory where you want to create your project, run create-next-app
to create a new Next.js app that you will be using for this guide.
npx create-next-app@latest nextjs-prisma
系统将提示你回答一些关于你项目的问题。选择所有默认值。
¥You will be prompted to answer a few questions about your project. Select all of the defaults.
作为参考,这些是:
¥For reference, those are:
-
TypeScript
-
ESLint
-
Tailwind CSS
-
无
src
目录¥No
src
directory -
应用路由
¥App Router
-
Turbopack
-
无自定义导入别名
¥No customized import alias
然后,导航到项目目录:
¥Then, navigate to the project directory:
cd nextjs-prisma
2. 安装和配置 Prisma
¥ Install and Configure Prisma
2.1.安装依赖
¥2.1. Install dependencies
要开始使用 Prisma,你需要安装一些依赖:
¥To get started with Prisma, you'll need to install a few dependencies:
- Prisma Postgres (recommended)
- Other databases
npm install prisma tsx --save-dev
npm install @prisma/extension-accelerate @prisma/client
npm install prisma tsx --save-dev
npm install @prisma/client
安装完成后,请在你的项目中初始化 Prisma:
¥Once installed, initialize Prisma in your project:
npx prisma init --db --output ../app/generated/prisma
在设置 Prisma Postgres 数据库时,你需要回答几个问题。选择离你最近的区域,并为数据库选择一个容易记住的名称,例如 "我的 __________ 项目"
¥You'll need to answer a few questions while setting up your Prisma Postgres database. Select the region closest to your location and a memorable name for your database like "My __________ Project"
这将造成:
¥This will create:
-
一个包含
schema.prisma
文件的prisma
目录。¥A
prisma
directory with aschema.prisma
file. -
一个 Prisma Postgres 数据库。
¥A Prisma Postgres database.
-
一个位于项目根目录、包含
DATABASE_URL
文件的.env
文件。¥A
.env
file containing theDATABASE_URL
at the project root. -
用于生成的 Prisma 客户端的
output
目录,用作app/generated/prisma
。¥An
output
directory for the generated Prisma Client asapp/generated/prisma
.
2.2.定义 Prisma Schema
¥2.2. Define your Prisma Schema
在 prisma/schema.prisma
文件中,添加以下模型:
¥In the prisma/schema.prisma
file, add the following models:
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
,它们之间存在一对多关系。
¥This creates two models: User
and Post
, with a one-to-many relationship between them.
2.3.配置 Prisma 客户端生成器
¥2.3. Configure the Prisma Client generator
现在,运行以下命令创建数据库表并生成 Prisma 客户端:
¥Now, run the following command to create the database tables and generate the Prisma Client:
npx prisma migrate dev --name init
2.4.种子数据库
¥2.4. Seed the database
添加一些种子数据,以便使用示例用户和帖子填充数据库。
¥Add some seed data to populate the database with sample users and posts.
在 prisma/
目录中创建一个名为 seed.ts
的新文件:
¥Create a new file called seed.ts
in the prisma/
directory:
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 如何运行此脚本:
¥Now, tell Prisma how to run this script by updating your 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,因为存在已知的 issue。通过更新你的 package.json
脚本,从你的开发脚本中移除 Turbopack
¥Before starting the development server, note that if you are using Next.js v15.2.0 or v15.2.1, do not use Turbopack as there is a known issue. Remove Turbopack from your dev script by updating your package.json
"script":{
"dev": "next dev --turbopack",
"dev": "next dev",
}
此更改在之前或之后的任何版本中都不需要。
¥This change is not needed on any versions before or after.
最后,运行 prisma db seed
以使用我们在 seed.ts
文件中定义的初始数据为你的数据库播种。
¥Finally, run prisma db seed
to seed your database with the initial data we defined in the seed.ts
file.
运行种子脚本:
¥Run the seed script:
npx prisma db seed
打开 Prisma Studio 检查你的数据:
¥And open Prisma Studio to inspect your data:
npx prisma studio
2.5 设置 Prisma 客户端
¥2.5 Set up Prisma Client
现在你已经有了一个包含一些初始数据的数据库,你可以设置 Prisma 客户端并将其连接到你的数据库。
¥Now that you have a database with some initial data, you can set up Prisma Client and connect it to your database.
在项目根目录下,创建一个新的 lib
目录,并向其中添加一个 prisma.ts
文件。
¥At the root of your project, create a new lib
directory and add a prisma.ts
file to it.
mkdir -p lib && touch lib/prisma.ts
现在,将以下代码添加到你的 lib/prisma.ts
文件中:
¥Now, add the following code to your lib/prisma.ts
file:
- Prisma Postgres (recommended)
- Other databases
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 客户端并将其附加到全局对象,以便在你的应用中仅创建该客户端的一个实例。这有助于解决在开发模式下将 Prisma ORM 与 Next.js 结合使用时可能出现的热重载问题。
¥This file creates a Prisma Client and attaches it to the global object so that only one instance of the client is created in your application. This helps resolve issues with hot reloading that can occur when using Prisma ORM with Next.js in development mode.
你将在下一部分中使用此客户端运行你的第一个查询。
¥You'll use this client in the next section to run your first queries.
3. 使用 Prisma ORM 查询数据库
¥ Query your database with Prisma ORM
现在你已经拥有一个初始化的 Prisma 客户端、一个数据库连接以及一些初始数据,你可以开始使用 Prisma ORM 查询数据。
¥Now that you have an initialized Prisma Client, a connection to your database, and some initial data, you can start querying your data with Prisma ORM.
在本例中,你将使应用的 "home" 页面显示所有用户。
¥In this example, you'll make the "home" page of your application display all of your users.
打开 app/page.tsx
文件并将现有代码替换为以下内容:
¥Open the app/page.tsx
file and replace the existing code with the following:
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>
);
}
这将为你提供一个包含标题和用户列表的基本页面。但是,该列表是静态的,并且包含硬编码值。让我们更新页面以从数据库中获取用户并使其动态化。
¥This gives you a basic page with a title and a list of users. However, that list is static with hardcoded values. Let's update the page to fetch the users from your database and make it dynamic.
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
模型,然后将其显示在列表中。
¥You are now importing your client, querying the User
model for all users, and then displaying them in a list.
现在,你的主页是动态的,并将显示数据库中的用户。
¥Now your home page is dynamic and will display the users from your database.
3.1 更新你的数据(可选)
¥3.1 Update your data (optional)
如果你想查看数据更新时会发生什么,你可以:
¥If you want to see what happens when data is updated, you could:
-
通过你选择的 SQL 浏览器更新你的
User
表¥update your
User
table via a SQL browser of your choice -
更改你的
seed.ts
文件以添加更多用户¥change your
seed.ts
file to add more users -
将对
prisma.user.findMany
的调用更改为重新排序用户、筛选用户或类似操作。¥change the call to
prisma.user.findMany
to re-order the users, filter the users, or similar.
只需重新加载页面即可看到更改。
¥Just reload the page and you'll see the changes.
4. 添加新的帖子列表页面
¥ Add a new Posts list page
你的主页已正常运行,但你需要添加一个显示所有帖子的新页面。
¥You have your home page working, but you should add a new page that displays all of your posts.
首先在 app
目录中创建一个新的 posts
目录,并在其中创建一个新的 page.tsx
文件。
¥First create a new posts
directory in the app
directory and create a new page.tsx
file inside of it.
mkdir -p app/posts && touch app/posts/page.tsx
其次,将以下代码添加到 app/posts/page.tsx
文件:
¥Second, add the following code to the app/posts/page.tsx
file:
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
可以加载,但内容再次被硬编码。让我们将其更新为动态的,类似于主页:
¥Now localhost:3000/posts
will load, but the content is hardcoded again. Let's update it to be dynamic, similarly to the home page:
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
来获取每篇文章的作者,以便显示作者的名称。
¥This works similarly to the home page, but instead of displaying users, it displays posts. You can also see that you've used include
in your Prisma Client query to fetch the author of each post so you can display the author's name.
此 "列表视图" 是 Web 应用中最常见的模式之一。你将在应用中添加另外两个页面,这两个页面通常也需要用到:一个 "详细视图" 和一个 "创建视图"。
¥This "list view" is one of the most common patterns in web applications. You're going to add two more pages to your application which you'll also commonly need: a "detail view" and a "create view".
5. 添加新的帖子详情页面
¥ Add a new Posts detail page
为了补充帖子列表页面,你将添加一个帖子详情页面。
¥To complement the Posts list page, you'll add a Posts detail page.
在 posts
目录中,创建一个新的 [id]
目录,并在其中创建一个新的 page.tsx
文件。
¥In the posts
directory, create a new [id]
directory and a new page.tsx
file inside of that.
mkdir -p app/posts/[id] && touch app/posts/[id]/page.tsx
此页面将显示单个帖子的标题、内容和作者。与其他页面一样,将以下代码添加到 app/posts/new/page.tsx
文件:
¥This page will display a single post's title, content, and author. Just like your other pages, add the following code to the app/posts/new/page.tsx
file:
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
将其更新为动态的:
¥As before, this page is static with hardcoded content. Let's update it to be dynamic based on the params
passed to the page:
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>
);
}
这里有很多变化,让我们来分解一下:
¥There's a lot of changes here, so let's break it down:
-
你将使用 Prisma 客户端通过
id
获取帖子,id
是从params
对象获取的。¥You're using Prisma Client to fetch the post by its
id
, which you get from theparams
object. -
如果帖子不存在(可能是已被删除或你输入了错误的 ID),则调用
notFound()
来显示 404 页面。¥In case the post doesn't exist (maybe it was deleted or maybe you typed a wrong ID), you call
notFound()
to display a 404 page. -
然后,你将显示帖子的标题、内容和作者。如果帖子没有内容,则会显示一条占位符消息。
¥You then display the post's title, content, and author. If the post doesn't have content, you display a placeholder message.
这不是最漂亮的页面,但它是一个好的开始。请尝试导航至 localhost:3000/posts/1
和 localhost:3000/posts/2
。你还可以通过导航到 localhost:3000/posts/999
来测试 404 页面。
¥It's not the prettiest page, but it's a good start. Try it out by navigating to localhost:3000/posts/1
and localhost:3000/posts/2
. You can also test the 404 page by navigating to localhost:3000/posts/999
.
6. 添加新的帖子创建页面
¥ Add a new Posts create page
要完善你的应用,你需要添加一个用于发布帖子的 "create" 页面。这将允许你撰写自己的帖子并将其保存到数据库中。
¥To round out your application, you'll add a "create" page for posts. This will let you write your own posts and save them to the database.
与其他页面一样,你将从静态页面开始,然后将其更新为动态页面。
¥As with the other pages, you'll start with a static page and then update it to be dynamic.
mkdir -p app/posts/new && touch app/posts/new/page.tsx
现在,将以下代码添加到 app/posts/new/page.tsx
文件中:
¥Now, add the following code to the app/posts/new/page.tsx
file:
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
函数以将帖子保存到数据库:
¥This form looks good, but it doesn't do anything yet. Let's update the createPost
function to save the post to the database:
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>
);
}
此页面现已具备功能表单!提交表单后,它将在数据库中创建新帖子,并将你重定向到帖子列表页面。
¥This page now has a functional form! When you submit the form, it will create a new post in the database and redirect you to the posts list page.
你还添加了一个 revalidatePath
调用来重新验证帖子列表页面,以便使用新帖子进行更新。这样,每个人都可以立即阅读新帖子。
¥You also added a revalidatePath
call to revalidate the posts list page so that it will be updated with the new post. That way everyone can read the new post immediately.
请尝试导航至 localhost:3000/posts/new
并提交表单。
¥Try it out by navigating to localhost:3000/posts/new
and submitting the form.
7. 将你的应用部署到 Vercel(可选)
¥ Deploy your application to Vercel (Optional)
将应用部署到 Vercel 的最快方法是使用 Vercel CLI。
¥The quickest way to deploy your application to Vercel is to use the Vercel CLI.
首先,安装 Vercel CLI:
¥First, install the Vercel CLI:
npm install -g vercel
然后,运行 vercel login
登录你的 Vercel 账户。
¥Then, run vercel login
to log in to your Vercel account.
vercel login
部署之前,你还需要告知 Vercel 已生成 Prisma 客户端。你可以通过将 postinstall
脚本添加到 package.json
文件来执行此操作。
¥Before you deploy, you also need to tell Vercel to make sure that the Prisma Client is generated. You can do this by adding a postinstall
script to your package.json
file.
{
"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
标志。
¥If you're not using Prisma Postgres, you need to remove the --no-engine
flag from the command.
完成此更改后,你可以通过运行 vercel
将应用部署到 Vercel。
¥After this change, you can deploy your application to Vercel by running vercel
.
vercel
部署完成后,你可以通过 Vercel 提供的 URL 访问你的应用。恭喜,你刚刚使用 Prisma ORM 部署了一个 Next.js 应用!
¥After the deployment is complete, you can visit your application at the URL that Vercel provides. Congratulations, you've just deployed a Next.js application with Prisma ORM!
8. 下一步
¥ Next steps
现在你已经拥有一个使用 Prisma ORM 运行的 Next.js 应用,你可以通过以下几种方式扩展和改进你的应用:
¥Now that you have a working Next.js application with Prisma ORM, here are some ways you can expand and improve your application:
-
添加身份验证以保护你的路由
¥Add authentication to protect your routes
-
添加编辑和删除帖子的功能
¥Add the ability to edit and delete posts
-
在帖子中添加评论
¥Add comments to posts
-
使用 Prisma 工作室 进行可视化数据库管理
¥Use Prisma Studio for visual database management
更多信息:
¥For more information:
Stay connected with Prisma
Continue your Prisma journey by connecting with our active community. Stay informed, get involved, and collaborate with other developers:
- Follow us on X for announcements, live events and useful tips.
- Join our Discord to ask questions, talk to the community, and get active support through conversations.
- Subscribe on YouTube for tutorials, demos, and streams.
- Engage on GitHub by starring the repository, reporting issues, or contributing to an issue.