Skip to main content

如何在 Next.js 中使用 Prisma ORM

20 min

介绍

¥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

  • Node.js 18+

  • 一个 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:

npm install prisma tsx --save-dev
npm install @prisma/extension-accelerate @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 a schema.prisma file.

  • 一个 Prisma Postgres 数据库。

    ¥A Prisma Postgres database.

  • 一个位于项目根目录、包含 DATABASE_URL 文件的 .env 文件。

    ¥A .env file containing the DATABASE_URL at the project root.

  • 用于生成的 Prisma 客户端的 output 目录,用作 app/generated/prisma

    ¥An output directory for the generated Prisma Client as app/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:

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,它们之间存在一对多关系。

¥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:

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 如何运行此脚本:

¥Now, tell Prisma how to run this script by updating your package.json:

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

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:

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 客户端并将其附加到全局对象,以便在你的应用中仅创建该客户端的一个实例。这有助于解决在开发模式下将 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:

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

这将为你提供一个包含标题和用户列表的基本页面。但是,该列表是静态的,并且包含硬编码值。让我们更新页面以从数据库中获取用户并使其动态化。

¥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.

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 模型,然后将其显示在列表中。

¥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:

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 可以加载,但内容再次被硬编码。让我们将其更新为动态的,类似于主页:

¥Now localhost:3000/posts will load, but the content is hardcoded again. Let's update it to be dynamic, similarly to the home page:

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 来获取每篇文章的作者,以便显示作者的名称。

¥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:

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 将其更新为动态的:

¥As before, this page is static with hardcoded content. Let's update it to be dynamic based on the params passed to the page:

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

这里有很多变化,让我们来分解一下:

¥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 the params 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/1localhost: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:

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 函数以将帖子保存到数据库:

¥This form looks good, but it doesn't do anything yet. Let's update the createPost function to save the post to the database:

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

此页面现已具备功能表单!提交表单后,它将在数据库中创建新帖子,并将你重定向到帖子列表页面。

¥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.

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 标志。

¥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:

We genuinely value your involvement and look forward to having you as part of our community!