跳转到主要内容

如何在 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 --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 架构,您需要一个数据库来应用您的架构。

我还没有数据库

如果您还没有数据库,您可以通过以下方式创建一个新的数据库。我们的入门指南中提供了分步说明。

当您创建 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 命令将您的架构应用于数据库。

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: '[email protected]',
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: '[email protected]',
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 进行可视化数据库管理

有关更多信息和更新