Database
使用数据库客户端
如何在服务端组件、API 路由和多租户上下文中通过 Prisma v7 客户端查询 PostgreSQL。
Nebutra 从 @nebutra/db 导出一个预配置的 Prisma 客户端。始终从该包导入 — 永远不要在应用程序代码中直接实例化 PrismaClient。
import { PrismaClient } from "@prisma/client";
import { withAccelerate } from "@prisma/extension-accelerate";
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const db =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
}).$extends(withAccelerate());
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = db;服务端组件查询
直接在 React 服务端组件中获取数据。内部数据访问不需要 API 层。
import { db } from "@nebutra/db";
export default async function ProjectsPage() {
const projects = await db.project.findMany({
orderBy: { createdAt: "desc" },
select: {
id: true,
name: true,
createdAt: true,
_count: { select: { members: true } },
},
});
return <ProjectList projects={projects} />;
}服务端组件在服务器上运行,因此数据库访问是安全的。永远不要在客户端组件("use client" 文件)中导入 @nebutra/db — 数据库凭据会暴露给浏览器。
多租户查询
在多租户上下文中,查询前用 withRls 包装 db 客户端。这会激活 PostgreSQL 行级安全,确保查询自动限定在当前租户范围内。
import { db } from "@nebutra/db";
import { getCurrentTenant, withRls } from "@nebutra/tenant";
export default async function ProjectsPage() {
const tenant = getCurrentTenant();
const tenantDb = withRls(db, tenant.tenantId);
// 只返回当前租户的项目
const projects = await tenantDb.project.findMany({
orderBy: { createdAt: "desc" },
});
return <ProjectList projects={projects} />;
}对于涉及租户所有数据的任何查询,始终使用 withRls(db, tenantId)。不使用 RLS 查询将返回所有租户的数据。
客户端数据获取
客户端组件不能导入 @nebutra/db。通过 API 路由获取数据:
"use client";
import { useEffect, useState } from "react";
export function useProjects() {
const [projects, setProjects] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch("/api/projects")
.then((res) => res.json())
.then((data) => {
setProjects(data.data);
setLoading(false);
});
}, []);
return { projects, loading };
}import { db } from "@nebutra/db";
import { getCurrentTenant, withRls } from "@nebutra/tenant";
import { NextResponse } from "next/server";
export async function GET() {
const tenant = getCurrentTenant();
const tenantDb = withRls(db, tenant.tenantId);
const projects = await tenantDb.project.findMany({
orderBy: { createdAt: "desc" },
});
return NextResponse.json({ success: true, data: projects });
}事务
当多个写操作必须一起成功或失败时,使用 Prisma 交互式事务:
import { db } from "@nebutra/db";
async function createOrganizationWithOwner(name: string, userId: string) {
return db.$transaction(async (tx) => {
const org = await tx.organization.create({
data: { name },
});
await tx.membership.create({
data: {
organizationId: org.id,
userId,
role: "OWNER",
},
});
await tx.auditLog.create({
data: {
organizationId: org.id,
action: "organization.created",
actorId: userId,
},
});
return org;
});
}错误处理
Prisma 抛出类型化错误。显式处理它们以返回有意义的响应:
import { Prisma } from "@prisma/client";
export function handlePrismaError(error: unknown): never {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
switch (error.code) {
case "P2002":
// 唯一约束违反
throw new Error(`该值的记录已存在。字段:${error.meta?.target}`);
case "P2025":
// 记录未找到
throw new Error("请求的记录不存在。");
case "P2003":
// 外键约束失败
throw new Error("此操作引用了一条不存在的记录。");
default:
throw new Error(`数据库错误:${error.code}`);
}
}
if (error instanceof Prisma.PrismaClientValidationError) {
throw new Error("提供给数据库查询的数据无效。");
}
throw error;
}常见 Prisma 错误代码:
| 代码 | 含义 |
|---|---|
P2002 | 唯一约束违反 |
P2003 | 外键约束失败 |
P2025 | 记录未找到(findUniqueOrThrow、update、delete) |
P2016 | 查询解释错误 |
P1001 | 无法连接到数据库服务器 |
P1002 | 数据库服务器超时 |
类型化查询
Prisma v7 为每个模型和查询生成完整的 TypeScript 类型。直接使用生成的类型:
import type { Prisma } from "@prisma/client";
// 从特定查询形状推断返回类型
type ProjectWithCount = Prisma.ProjectGetPayload<{
include: {
_count: { select: { members: true } };
};
}>;
// 用于组件 props
interface ProjectCardProps {
project: ProjectWithCount;
}使用 Prisma.XxxGetPayload 从查询的 select 或 include 形状派生精确类型。这避免了维护可能与架构偏离的手动接口定义。
How is this guide?
在 GitHub 上编辑此页面
最后更新于