首页 / 文章

使用 Prisma 和 PostgreSQL 的 Next.js 服务器端操作

Next.js Server Actions 直接与共享的 Prisma 客户端及 PostgreSQL 进行交互,因此无需 API 路由或客户端 fetch 调用即可实现任务的 CRUD 操作。

发布者:REACTAPP.TOP阅读约 4 分钟917 词

来源: 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 层。