1. Prisma 安装与初始化
在 Next.js 项目中集成关系型数据库时,Prisma 提供类型安全的客户端与迁移工作流。先安装 CLI 与运行时依赖:
npm install prisma @prisma/client
在项目根目录执行初始化,会创建默认配置与示例环境文件:
npx prisma init
常见生成文件:
prisma/schema.prisma:数据模型、数据源与生成器配置。.env:存放DATABASE_URL等敏感连接信息(应加入.gitignore)。
在 .env 中配置连接串,按所选数据库调整协议与参数:
# PostgreSQL 示例
DATABASE_URL="postgresql://USER:PASSWORD@localhost:5432/mydb?schema=public"
# MySQL 示例
# DATABASE_URL="mysql://USER:PASSWORD@localhost:3306/mydb"
# SQLite(本地文件,适合学习与原型)
# DATABASE_URL="file:./dev.db"
Prisma 支持 PostgreSQL、MySQL、SQLite、SQL Server、CockroachDB、MongoDB 等;在 schema.prisma 的 datasource 中指定 provider 即可。
💡 要点
Prisma 是 Next.js 生态中最流行的 TypeScript ORM 之一:Schema 即类型来源,迁移与客户端生成一体化,与 App Router、Server Actions 配合顺畅。
2. Schema 定义
prisma/schema.prisma 由三部分典型内容组成:datasource(连哪个数据库)、generator(如何生成 Prisma Client)、以及若干 model(表结构与关系)。
- datasource:
provider与url = env("DATABASE_URL")。 - generator:通常
provider = "prisma-client-js",生成 Node 可用的客户端。 - Model:对应数据表;字段类型包括
String、Int、Boolean、DateTime、Json、Bytes等;可用@id、@default、@unique、@updatedAt等属性。 - 关联:使用字段类型引用另一 Model,并用
@relation声明外键与关系名;常见为一对多(如 User → Post);多对多通过中间表(显式 Model)连接两端。
下面是一份包含 User 与 Post、一对多关系的完整示例(PostgreSQL)。Prisma 使用自有 DSL,以下用 TypeScript 风格高亮近似展示(亦可安装编辑器 Prisma 插件获得准确着色):
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
role String @default("USER")
profile Json?
createdAt DateTime @default(now())
posts Post[]
}
model Post {
id String @id @default(cuid())
title String
published Boolean @default(false)
content String?
authorId String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([authorId])
}
执行 prisma migrate dev 后,迁移目录中会生成面向真实数据库的 SQL(PostgreSQL 示意):
-- 示意:由 Prisma 生成的迁移文件中的 SQL 片段
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"name" TEXT,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
说明:Post.authorId 为外键;User.posts 与 Post.author 构成双向导航。多对多可再增加 Tag 与 PostTag 中间表并双向 @relation。
3. Migration 与数据库同步
修改 Schema 后,需要让数据库结构与之对齐,并重新生成类型安全的客户端。
迁移(推荐用于团队与可追溯历史)
npx prisma migrate dev --name init
该命令会基于当前 schema.prisma 生成 SQL 迁移文件、应用到开发数据库,并执行 prisma generate。
快速推送(原型阶段)
npx prisma db push
不生成迁移历史,直接将 Schema 同步到数据库,适合个人实验或临时环境;生产环境更推荐 migrate 工作流。
可视化与客户端
npx prisma studio
npx prisma generate
- Prisma Studio:浏览器中浏览、编辑表数据,便于调试。
- generate:根据 Schema 生成/更新
node_modules/.prisma/client;CI 或部署前常显式执行。
典型开发流程:改 Schema → migrate dev(或 db push)→ 在代码中使用 prisma 客户端。拉取他人迁移后执行 npx prisma migrate deploy(生产)同步数据库。
4. Prisma Client 单例
问题:Next.js 开发模式下模块会随热重载多次执行,若每次新建 PrismaClient,可能堆积大量数据库连接。
解决:在全局对象上缓存单例,仅在不存在时创建;生产环境仍建议配合连接池(如 PgBouncer)与托管数据库限制。
创建 src/lib/prisma.ts:
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined };
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
});
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
之后在 Server Components、Route Handlers、Server Actions 中统一 import { prisma } from "@/lib/prisma"(路径别名需在 tsconfig.json 中配置 @/*)。
5. CRUD 操作
以下假设已按上文定义 User / Post 模型并生成客户端。
创建(Create)
import { prisma } from "@/lib/prisma";
await prisma.user.create({
data: {
email: "dev@example.com",
name: "开发者",
},
});
await prisma.post.create({
data: {
title: "第一篇",
authorId: userId,
},
});
查询(Read)
const users = await prisma.user.findMany({
where: { role: "USER" },
orderBy: { createdAt: "desc" },
});
const one = await prisma.user.findUnique({
where: { email: "dev@example.com" },
});
const first = await prisma.post.findFirst({
where: { published: true },
});
更新(Update)
await prisma.user.update({
where: { id: userId },
data: { name: "新名字" },
});
await prisma.post.updateMany({
where: { authorId: userId },
data: { published: true },
});
删除(Delete)
await prisma.post.delete({
where: { id: postId },
});
关联查询:include / select
const postsWithAuthor = await prisma.post.findMany({
include: { author: true },
});
const slim = await prisma.user.findMany({
select: { id: true, email: true, _count: { select: { posts: true } } },
});
分页:skip / take
const page = 2;
const pageSize = 10;
const pageRows = await prisma.post.findMany({
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: { createdAt: "desc" },
});
6. 结合 Server Actions
在 App Router 中,可在服务器函数(带 "use server")内直接调用 prisma,实现表单提交与变更后列表刷新。下面示例包含:创建帖子表单、列表由 Server Component 查询、删除由 Server Action 触发,并用 revalidatePath 使缓存页面失效。
app/posts/actions.ts
"use server";
import { revalidatePath } from "next/cache";
import { prisma } from "@/lib/prisma";
export async function createPost(formData: FormData) {
const title = String(formData.get("title") ?? "").trim();
const authorId = String(formData.get("authorId") ?? "").trim();
if (!title || !authorId) return;
await prisma.post.create({
data: { title, authorId, published: false },
});
revalidatePath("/posts");
}
export async function deletePost(postId: string) {
await prisma.post.delete({ where: { id: postId } });
revalidatePath("/posts");
}
app/posts/page.tsx(列表 + 表单 + 删除)
import { prisma } from "@/lib/prisma";
import { createPost, deletePost } from "./actions";
export default async function PostsPage() {
const posts = await prisma.post.findMany({
orderBy: { createdAt: "desc" },
include: { author: { select: { name: true, email: true } } },
});
const users = await prisma.user.findMany({ select: { id: true, email: true } });
return (
<div className="mx-auto max-w-2xl space-y-8 p-6">
<form action={createPost} className="space-y-3 rounded border p-4">
<input name="title" placeholder="标题" className="w-full border px-2 py-1" required />
<select name="authorId" className="w-full border px-2 py-1" required>
{users.map((u) => (
<option key={u.id} value={u.id}>
{u.email}
</option>
))}
</select>
<button type="submit" className="rounded bg-black px-3 py-1 text-white">
创建帖子
</button>
</form>
<ul className="space-y-2">
{posts.map((p) => (
<li key={p.id} className="flex items-center justify-between border-b py-2">
<div>
<div className="font-medium">{p.title}</div>
<div className="text-sm text-gray-500">
{p.author.email} · {p.createdAt.toISOString()}
</div>
</div>
<form action={deletePost.bind(null, p.id)}>
<button type="submit" className="text-sm text-red-600">
删除
</button>
</form>
</li>
))}
</ul>
</div>
);
}
说明:createPost 与 deletePost 仅在服务器执行;页面默认为 Server Component,数据在服务端读取。变更后 revalidatePath("/posts") 会触发该路径的重新渲染与数据更新。
7. 环境变量配置
- .env:本地默认值;务必加入
.gitignore,勿提交到 Git。 - .env.local:Next.js 会加载且优先级更高,常用于个人机器上的覆盖(同样不应提交)。
- Vercel:在项目 Settings → Environment Variables 中为 Preview / Production 配置
DATABASE_URL等;部署后重新构建使新变量生效。
示例(仅结构示意,真实密码请替换):
{
"NOTE": "实际使用 .env 文件,每行 KEY=VALUE;此处为说明用 JSON 结构",
"DATABASE_URL": "postgresql://user:password@host:5432/dbname"
}
💡 要点
永远不要将数据库密码、API 密钥硬编码在源码中;一律通过环境变量注入,并在托管平台与密钥管理工具中轮换。
8. Drizzle ORM 简介
Drizzle 是另一款流行的 TypeScript 数据层方案,偏「SQL 风格」与轻量运行时,常与 drizzle-kit 迁移工具配合使用。
Prisma vs Drizzle ORM
- Prisma:声明式 Schema、迁移与 Studio 一体;API 偏高层封装;生态与教程丰富。
- Drizzle:更贴近 SQL 的链式/函数 API;包体积与运行时通常更轻;性能敏感或强 SQL 控制场景有优势。
Drizzle 查询示例(概念演示):
import { eq } from "drizzle-orm";
import { db } from "./db";
import { posts } from "./schema";
export async function listPublished() {
return db.select().from(posts).where(eq(posts.published, true));
}
选择建议:团队熟悉 SQL、希望最小抽象与细粒度控制时可评估 Drizzle;追求快速建模、一体化工具链与大量现成资料时,Prisma 仍是稳妥起点。二者都可与 Next.js Server Components / Server Actions 配合。
9. 本章要点
安装与配置
prisma + @prisma/client,prisma init 生成 Schema 与 .env;DATABASE_URL 指向目标数据库。
Schema 与关系
在 schema.prisma 中定义 Model、字段类型与 @relation;一对多 / 多对多按业务建模。
迁移与工具
migrate dev 管理版本;db push 快速同步;studio 管理数据;generate 更新客户端。
单例与 CRUD
src/lib/prisma.ts 全局单例避免开发环境连接风暴;掌握 create / findMany / update / delete、include、skip+take。
Server Actions
在 "use server" 中调用 Prisma,表单 action 提交;配合 revalidatePath 刷新列表。
安全与选型
密钥只放环境变量;了解 Drizzle 等替代方案,按团队背景在 Prisma 与轻量 SQL 风格 ORM 间取舍。