如何在 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 --output ../src/app/generated/prisma
这将在您的项目根目录中创建一个新的 prisma
目录,其中包含一个 schema.prisma
文件。schema.prisma
文件是您定义数据库模型的地方。
prisma init
命令还在项目根目录中创建一个 .env
文件来存储您的数据库连接字符串,并将生成的客户端输出到 /app/generated/prisma
。
接下来,向您的 schema.prisma
文件添加两个模型。一个 User
模型和一个 Post
模型。
generator client {
provider = "prisma-client-js"
output = "../src/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])
}
这代表一个简单的博客,包含用户和帖子。每个 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 '../src/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()
现在,将 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"
}
}
在启动开发服务器之前,请注意,如果您使用的是 Next.js v15.2.0,请不要使用 Turbopack,因为存在一个已知的问题。通过更新 package.json
从开发脚本中删除 Turbopack。
"script":{
"dev": "next dev --turbopack",
"dev": "next dev",
}
最后,运行 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 src/lib && touch src/lib/prisma.ts
现在,将以下代码添加到您的 lib/prisma.ts
文件中
import { PrismaClient } from '../src/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 Client 并将其附加到全局对象,以便在应用程序中只创建客户端的一个实例。这有助于解决在开发模式下使用 Prisma ORM 和 Next.js 时可能出现的热重载问题。
如果您没有使用 Prisma Postgres,请替换以下行:
const prisma = globalForPrisma.prisma || new PrismaClient().$extends(withAccelerate())
为
const prisma = globalForPrisma.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 src/app/posts && touch src/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 src/app/posts/[id] && touch src/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
获取帖子,id
从params
对象获取。 - 如果帖子不存在(可能已被删除或您输入了错误的 ID),我们调用
notFound()
显示 404 页面。 - 然后我们显示帖子的标题、内容和作者。如果帖子没有内容,我们显示一个占位符消息。
这不是最漂亮的页面,但这是一个好的开始。尝试导航到 localhost:3000/posts/1
和 localhost:3000/posts/2
。您也可以导航到 localhost:3000/posts/999
测试 404 页面。
6. 添加一个新的帖子创建页面
为了完善我们的应用程序,我们将添加一个帖子的“创建”页面。这将允许您编写自己的帖子并将其保存到数据库中。
像其他页面一样,我们将从静态页面开始,然后将其更新为动态的。
mkdir -p src/app/posts/new && touch src/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 的联系
通过与我们的社区联系,继续您的 Prisma 之旅: 我们活跃的社区。保持关注,积极参与,与其他开发者协作
- 在 X 上关注我们 获取公告、直播活动和实用技巧。
- 加入我们的 Discord 提问,与社区交流,并通过对话获得积极支持。
- 在 YouTube 上订阅 获取教程、演示和直播。
- 在 GitHub 上互动 通过给仓库加星、报告问题或贡献代码参与其中。