使用 Prisma 和 PostgreSQL 的 Next.js 服务器端操作
Next.js Server Actions 直接与共享的 Prisma 客户端及 PostgreSQL 进行交互,因此无需 API 路由或客户端 fetch 调用即可实现任务的 CRUD 操作。
来源: Next.js Server Actions + Prisma + PostgreSQL: The Integration Pattern You Actually Need
常见的教程会将 Next.js、Prisma 和 PostgreSQL 分别作为独立的组件来讲解:搭建应用框架、生成客户端代码、连接数据库、执行数据迁移。但缺失的是如何将这三层整合为一种统一的架构模式,以及为何这种模式能大幅简化传统全栈开发中的繁琐流程。
各层的职责十分明确:Server Actions 负责处理服务器端操作,Prisma 用于生成类型安全的查询语句,PostgreSQL 则负责存储数据。每一层都专注处理自身的任务,这样一来就省去了中间层的诸多复杂设计——无需自定义 API 路由,无需手动编写 fetch 代码,React 组件与数据库之间也无需额外的序列化处理。
只需一个简单的任务管理器,就能完整展示从数据结构设计到功能界面实现的整个流程,而无需为 CRUD 操作编写专门的路由处理函数。
为何选择这套技术栈
在完整的 Next.js 应用中,服务器端操作、Prisma 和 PostgreSQL 在每一层都相互配合。
由于操作在服务器端执行,因此可以直接导入 Prisma 客户端,而无需将数据库凭据暴露给浏览器。Prisma 提供了类型化的查询接口,这样写入 PostgreSQL 的数据与从数据库读取的数据在 TypeScript 体系中能够保持一致。PostgreSQL 具有高可靠性、广泛的兼容性,并且能与 Prisma 的迁移工作流完美配合。
其优势在于,从点击按钮到数据持久化存储的路径更为简短——通常相比 Next.js 生态系中的其他方法,所需的连接代码更少。
项目设置
首先创建一个新的 Next.js 应用,然后添加 Prisma:
npx create-next-app@latest my-app --typescript
cd my-app
npm install prisma @prisma/client
npx prisma init
The prisma init command creates a prisma/ folder with a schema.prisma file and a .env file. Open .env and add your PostgreSQL connection string:
```DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/mydb"
3. Define Your Schema
Open prisma/schema.prisma and define your data model. We will keep it simple — a Task model:
```generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Task {
id String @id @default(cuid())
title String
completed Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Run the migration to create the table:
```npx prisma migrate dev --name init
This creates the table and generates the Prisma Client. You are ready to query.
集成模式
这就是核心思想。Server Action会导入Prisma客户端并直接与数据库通信——无需API路由,无需客户端的fetch调用,UI与数据持久层之间也没有额外的序列化层。首先,导出一个可在开发时热重载过程中保持不变的共享Prisma实例:
// lib/prisma.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const prisma =
globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production')
globalForPrisma.prisma = prisma;
接着定义Server Actions模块:
// app/tasks/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
import { prisma } from '@/lib/prisma';
export async function getTasks() {
return prisma.task.findMany({ orderBy: { createdAt: 'desc' } });
}
5. Wiring It to Your UI
Now use the Server Actions in your page and components:
```// app/tasks/page.tsx
import { getTasks } from './actions';
import { TaskForm } from './TaskForm';
import { TaskList } from './TaskList';
export default async function TasksPage() {
const tasks = await getTasks();
return (
<main>
<h1>My Tasks</h1>
<TaskForm />
<TaskList tasks={tasks} />
</main>
);
}
export async function createTask(formData: FormData) {
const title = formData.get('title') as string;
if (!title?.trim()) return { error: 'Title is required' };
await prisma.task.create({ data: { title } });
revalidatePath('/tasks');
}
export async function toggleTask(id: string, completed: boolean) {
await prisma.task.update({ where: { id }, data: { completed: !completed } });
revalidatePath('/tasks');
}
export async function deleteTask(id: string) {
await prisma.task.delete({ where: { id } });
revalidatePath('/tasks');
}
将其与UI连接
从页面中调用这些动作。服务器组件可以直接调用getTasks()——无需useEffect,无需手动设置加载标志,也无需客户端端的fetch操作:
// app/tasks/page.tsx
import { getTasks } from './actions';
import { TaskForm } from './TaskForm';
import { TaskList } from './TaskList';
export default async function TasksPage() {
const tasks = await getTasks();
return (
<main>
<h1>My Tasks</h1>
<TaskForm />
<TaskList tasks={tasks} />
</main>
);
}
总结
Server Actions 结合 Prisma 与 PostgreSQL,几乎无需任何样板代码即可构建完整的全栈数据处理流程。组件调用函数,该函数通过 Prisma 进行读写操作,PostgreSQL 负责存储数据,而 revalidatePath 则负责刷新用户界面——通常只需几十行代码,无需额外的 API 层。