如何在 Next.js 中使用 Prisma ORM
介绍
本页面回答的问题
- 如何在 Next.js 中设置 Prisma?
- 如何将 Next.js 与 Prisma 部署到 Vercel?
- 如何处理生产环境中的迁移?
本指南将向你展示如何在 Next.js 15(一个全栈 React 框架)中使用 Prisma。你将学习如何创建一个 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
- 没有自定义导入别名
然后,导航到项目目录
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 数据库时,你需要回答几个问题。选择离你位置最近的区域,并为你的数据库取一个好记的名字,例如“我的 __________ 项目”
这将创建
- 一个包含
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 将 dotenv 添加到 prisma.config.ts
要访问 .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. 填充数据库
添加一些填充数据以使用示例用户和帖子填充数据库。
在 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 来从你的开发脚本中删除 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 并将其附加到全局对象,以便你的应用程序中只创建一个客户端实例。这有助于解决在使用 Prisma ORM 和 Next.js 的开发模式下可能出现的热重载问题。
你将在下一节中使用此客户端来运行你的第一个查询。
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 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 来获取每个帖子的作者,以便你可以显示作者的姓名。
这种“列表视图”是 Web 应用程序中最常见的模式之一。你将向你的应用程序添加另外两个页面,它们也是你通常需要的:“详细视图”和“创建视图”。
5. 添加一个新的帖子详情页
为了补充帖子列表页,你将添加一个帖子详情页。
在 posts 目录中,创建一个新的 [id] 目录,并在其中创建一个新的 page.tsx 文件。
mkdir -p "app/posts/[id]" && touch "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]">{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获取帖子,你从params对象中获取该id。 - 如果帖子不存在(可能被删除或你输入了错误的 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。你可以通过在 package.json 文件中添加 postinstall 脚本来实现这一点。
{
"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. 后续步骤
现在你已经有了一个带有 Prisma ORM 的 Next.js 应用程序,以下是一些你可以扩展和改进你的应用程序的方法
- 添加身份验证以保护你的路由
- 添加编辑和删除帖子的功能
- 为帖子添加评论
- 使用 Prisma Studio 进行可视化数据库管理
更多信息
与 Prisma 保持联系
通过以下方式与我们保持联系,继续你的 Prisma 之旅: 我们的活跃社区。保持信息灵通,参与其中,并与其他开发人员协作。
- 在 X 上关注我们 获取公告、直播活动和实用技巧。
- 加入我们的 Discord 提问、与社区交流,并通过对话获得积极支持。
- 在 YouTube 上订阅 获取教程、演示和直播。
- 在 GitHub 上参与 加星收藏存储库、报告问题或为问题做出贡献。