☰
在 Nuxt.js 中接入 Prisma Accelerate:基于 prisma-examples 的四种缓存策略实战指南
2026/10/15 15:58:28 网站建设 项目流程
  • 示例工程

【免费下载链接】prisma-examples

🚀 Ready-to-run Prisma example projects

项目地址:https://gitcode.com/gh_mirrors/pr/prisma-examples
点击查看免费下载

本篇指南以 prisma-examples 仓库中的 accelerate/nuxtjs-starter 项目为蓝本,讲解如何在 Nuxt.js 应用中通过 Prisma ORM 与 Prisma Accelerate 的组合,构建一个带多种缓存策略的"语录生成器"(Quote Generator)。读完本文,你将掌握 Accelerate 的 TTL、SWR、TTL+SWR 与无缓存四种策略的配置方式、cacheStrategy与withAccelerateInfo()的底层调用链,以及如何从运行指标中判断缓存命中状态,从而在真实项目中权衡性能与数据新鲜度。

项目概览:语录生成器如何工作

该项目是一个可直接运行的 Nuxt.js 应用:页面从预置的名言集中检索最近新增的一条名言,并分别以"开启缓存"和"不开启缓存"两种方式返回,用于直观对比 Accelerate 各缓存策略对数据库压力的影响。其核心流程可以拆解为三个步骤:

  1. Fetch a quote(取名言):检索最近添加的名言(按id倒序取第一条),分别在有缓存与无缓存两种路径下执行;
  2. Apply caching strategy(应用缓存策略):根据请求参数决定采用 TTL、SWR、TTL+SWR 或完全不缓存中的哪一种策略;
  3. Return the quote(返回结果):返回名言本身,同时附带缓存状态、缓存节点区域、命中时间等元数据。

从项目源码结构看,这个"三步走"分别落在三个不同的代码层:API 路由(server/api/quotes.ts)负责解析缓存策略参数,查询层(lib/utils/query.ts)负责携带cacheStrategy执行 Prisma 查询并采集性能信息,视图层(pages/index.vue)负责并行发起四种策略的请求并渲染结果。README 中明确指出,该项目演示了 Accelerate 提供的全部缓存策略,是学习 Accelerate 缓存体系的最小完整范例。

前置条件

在开始之前,你需要准备以下两项:

  • 连接串:一个公网可访问数据库的连接串(示例项目使用 PostgreSQL,见下文 Schema 分析);
  • Accelerate 连接串:包含Accelerate API Key的 Accelerate 连接串。你需要在 Prisma Data Platform 中为项目启用 Accelerate 后获取。Accelerate 连接串具有如下固定结构:
prisma://accelerate.prisma-data.net/?api_key=__YOUR_ACCELERATE_API_KEY__

其中__YOUR_ACCELERATE_API_KEY__是你在 Prisma Data Platform 生成的 API Key。这条连接串会同时承担"数据库代理 + 连接池 + 缓存层"三重职责,Prisma Client 的所有查询都经由它转发。

从零跑起来:完整启动步骤

1. 克隆仓库并安装依赖

克隆 prisma-examples 仓库(可使用--depth=1浅克隆以加速),进入示例目录并安装依赖:

git clone https://gitcode.com/gh_mirrors/pr/prisma-examples.git --depth=1 cd prisma-examples/accelerate/nuxtjs-starter npm install

依赖清单以 package.json 为准,核心依赖包括:nuxt3.19.0、@prisma/client7.0.0、@prisma/extension-accelerate2.0.2(Accelerate 客户端扩展)、@prisma/nuxt0.3.0(Nuxt 集成模块)、zod3.25.76(参数校验)以及tsx(用于运行种子脚本)。

2. 配置环境变量

在项目根目录创建.env文件,并写入两个变量:

touch .env
# .env # Accelerate connection string (used for queries by Prisma Client) DATABASE_URL="__YOUR_ACCELERATE_CONNECTION_STRING__" NUXT_PUBLIC_URL="http://localhost:3000"
  • DATABASE_URL:填入上一步获取的 Accelerate 连接串(形如prisma://accelerate.prisma-data.net/?api_key=...)。注意__YOUR_ACCELERATE_CONNECTION_STRING__是占位符,必须替换为真实值;
  • NUXT_PUBLIC_URL:前端页面发起请求时使用的服务地址,默认http://localhost:3000。该变量在 lib/utils/helper.ts 中被读取并作为HOST导出,供页面层拼接 API 地址使用。

值得一提的是,示例采用新版 prisma.config.ts 集中管理 Prisma 配置:schema指向prisma/schema.prisma,migrations.path指向迁移目录,seed命令为tsx ./prisma/seed.ts,而datasource.url通过env("DATABASE_URL")读取。这意味着 Schema 中无需再内联数据库地址,配置职责更清晰。

3. 执行迁移建表并写入种子数据

Prisma Schema 中定义了一个Quotes模型。执行以下命令将该模型映射到数据库并创建对应的表:

npx prisma migrate dev --name init

执行成功后数据库中将出现一张空的Quotes表。接着运行种子脚本写入示例数据:

npx prisma db seed

种子逻辑位于 prisma/seed.ts:使用prisma.quotes.createMany({ skipDuplicates: true })一次性写入 20 条名人名言(如 "The only way to do great work is to love what you do."、"You miss 100% of the shots you don't take." 等),skipDuplicates保证重复执行不会产生重复记录。

4. 生成 Prisma Client

执行生成命令,产出类型安全的客户端代码:

npx prisma generate

本项目采用 Prisma 7 的prisma-client生成器,输出目录为./prisma/generated(见 prisma/schema.prisma),生成的客户端随后会被 lib/prisma.ts 与种子脚本引用。

5. 启动开发服务器

npm run dev

由于schema与迁移目录都已就绪,启动时会跳过"安装 Prisma CLI""创建 Prisma Schema"等引导提示。此外,nuxt.config.ts 中通过prisma: { autoSetupPrisma: false }关闭了自动初始化,避免重复引导。但 Nuxt DevTools 仍会询问是否安装 Prisma Studio 用于可视化编辑数据:

Do you want to view and edit your data by installing Prisma Studio in Nuxt DevTools? › (Y/n)

选择Y即可在 DevTools 中直接浏览与编辑Quotes表。

6. 在浏览器中验证效果

打开浏览器访问http://localhost:3000,即可看到"Accelerated Quotes"页面。页面底部会展示四种缓存策略各自的性能与统计信息(如缓存命中状态 cache/hit):

深入源码:缓存策略在 API 层如何落地

缓存策略的分发入口是 server/api/quotes.ts。该文件基于 Nuxt 3 / h3 的defineEventHandler实现,完整逻辑如下:

import { defineEventHandler, getQuery, createError } from 'h3'; import { z } from 'zod'; import { getQuotes } from '@/lib/utils/query'; export default defineEventHandler(async (event) => { const { cache } = getQuery(event); const parser = z.enum(["TTL", "SWR", "BOTH", "NONE"]); const parsedOutput = await parser.safeParseAsync(cache); if (!parsedOutput.success) { throw createError({ statusCode: 400, statusMessage: "Invalid search parameter." }); } let map = new Map(); map.set("TTL", { ttl: 30 }); map.set("SWR", { swr: 30 }); map.set("BOTH", { ttl: 30, swr: 60 }); map.set("NONE", undefined); const data = await getQuotes(map.get(parsedOutput.data)); return data; });

这段代码揭示了几个关键设计:

  • 参数白名单校验:cache查询参数必须严格属于TTL / SWR / BOTH / NONE四选一,使用z.enum配合safeParseAsync做异步校验,非法参数直接返回400 Invalid search parameter,避免误传参数导致意外缓存行为;
  • 策略到参数对象的映射:四种策略被映射为cacheStrategy选项——
    • TTL:{ ttl: 30 },30 秒内命中缓存直接返回,缓存未过期前不访问数据库;
    • SWR:{ swr: 30 },Stale-While-Revalidate,过期后先返回旧值再后台刷新,兼顾响应速度与数据更新;
    • BOTH:{ ttl: 30, swr: 60 },TTL 与 SWR 组合,30 秒内强缓存,30~60 秒间以 SWR 方式返回旧数据并异步更新;
    • NONE:undefined,不启用任何缓存,仅使用 Accelerate 的连接池能力,作为对照基线。
  • 单一查询函数复用:四种策略共用同一个getQuotes(),仅在入参上区分,代码零重复。

从查询层 lib/utils/query.ts 可以看到cacheStrategy的完整用法:

export const getQuotes = async (strategy?: CacheStrategy) => { const start = Date.now(); const result = await prisma.quotes .findMany({ cacheStrategy: strategy, orderBy: { id: "desc" }, take: 1, }) .withAccelerateInfo(); return { data: result?.data?.[0], info: result.info, time: Date.now() - start, }; };

要点如下:

  • cacheStrategy作为 Prisma 查询选项直接传给findMany,不传或传undefined时只走连接池、不走缓存,这正是NONE策略的实现方式;
  • .withAccelerateInfo()是 Accelerate 扩展提供的链式方法,查询完成后会追加一个info字段,其中包含缓存状态、缓存节点区域、请求 ID 等诊断信息;
  • 查询按id: "desc"倒序并take: 1,语义为"最近新增的一条名言";
  • 返回值同时携带data(名言本体)、info(Accelerate 元数据)与time(从发起到返回的耗时毫秒数),time供页面层直观展示性能对比。

缓存策略与信息类型的完整定义

lib/types/index.ts 给出了这套机制的类型化契约,是理解全链路的关键:

export type CacheStrategy = | { ttl: number; swr: number } | { ttl: number } | { swr: number }; export type AccelerateInfo = { cacheStatus: "ttl" | "swr" | "miss" | "none"; lastModified: Date; region: string; requestId: string; signature: string; }; export type Quote = { id: number; quote: string; createdAt: string; }; export type QuoteResult = { data: Quote; info: AccelerateInfo; time: number; }; export type QuoteCacheType = "SWR" | "TTL" | "No caching" | "TTL + SWR";

其中AccelerateInfo.cacheStatus是判断缓存行为的核心指标,取值含义如下:

cacheStatus含义是否命中缓存
ttl命中 TTL 缓存,直接返回缓存数据是(CACHE HIT)
swr命中 SWR 缓存,返回旧数据并触发后台刷新是(CACHE HIT)
miss缓存未命中,回源数据库查询否
none未启用缓存(即NONE策略)否

lastModified表示缓存数据的最后修改时间,region表示处理请求的 Accelerate 缓存节点区域,requestId可用于跨端排查单次请求。

客户端如何构建与缓存诊断信息对接

lib/prisma.ts 负责创建经过 Accelerate 扩展的 Prisma Client 单例:

import { PrismaClient } from '../prisma/generated/client'; import { withAccelerate } from '@prisma/extension-accelerate' const prismaClientSingleton = () => { return new PrismaClient({ accelerateUrl: process.env.DATABASE_URL, }).$extends(withAccelerate()) } const prisma = globalThis.prismaGlobal ?? prismaClientSingleton() export default prisma if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma

这里有两个值得注意的实现细节:

  • accelerateUrl注入:将.env中的DATABASE_URL(即 Accelerate 连接串)作为accelerateUrl传入PrismaClient构造器,再通过$extends(withAccelerate())挂载 Accelerate 能力。这也解释了为何 prisma/schema.prisma 的datasource中只有provider = "postgresql"而没有url——连接地址统一由prisma.config.ts与运行时环境变量管理;
  • 开发环境热更新单例:沿用 Next.js 社区经典的globalThis缓存模式,在非生产环境下将实例挂到globalThis,避免 Nuxt 开发模式热更新时反复创建 Prisma Client 导致连接数膨胀。种子脚本 prisma/seed.ts 使用了相同的new PrismaClient({ accelerateUrl }).$extends(withAccelerate())写法。

前端视图:四种策略同屏对比

页面层 pages/index.vue 使用 Nuxt 内置的useFetch并行发起四个请求,恰好对应四种缓存策略:

const { data: ttl } = useFetch<QuoteResult>(() => `${HOST}/api/quotes?cache=TTL`); const { data: swr } = useFetch<QuoteResult>(() => `${HOST}/api/quotes?cache=SWR`); const { data: both } = useFetch<QuoteResult>(() => `${HOST}/api/quotes?cache=BOTH`); const { data: none } = useFetch<QuoteResult>(() => `${HOST}/api/quotes?cache=NONE`);

四个卡片分别渲染"TTL""SWR""TTL + SWR""No caching"的结果。卡片组件 components/Quote.vue 是缓存诊断信息的前端呈现层,其判定逻辑直接复用了cacheStatus的语义:

const cacheStatus = computed(() => props.result?.info?.cacheStatus || '');
<span class="font-bold" :class="{ 'text-green-400': cacheStatus === 'swr' || cacheStatus === 'ttl', 'text-red-400': cacheStatus !== 'swr' && cacheStatus !== 'ttl' }"> {{ cacheStatus?.toUpperCase() }} {{ cacheStatus === 'swr' || cacheStatus === 'ttl' ? ' CACHE HIT' : '' }} </span>

即:当cacheStatus为swr或ttl时以绿色显示并标注CACHE HIT,其余(miss/none)以红色显示。除此之外,卡片还展示以下指标:

  • Cache Node Region:处理本次请求的 Accelerate 缓存节点区域(来自info.region),可帮助确认请求是否被就近的缓存节点承接;
  • Cached Modified at:缓存数据的最后修改时间(来自info.lastModified);
  • Cache status:上述cacheStatus的命中判定;
  • Time taken:单次请求耗时(result.time,毫秒),用于直观对比缓存与直连数据库的延迟差异。

外层布局由 app.vue 提供:"Accelerated Quotes" 标题、说明文案以及一个Refresh按钮(内部实现为window.location.reload()),方便反复刷新观察缓存命中状态随时间推移的变化——例如 TTL 30 秒过期后再次刷新,cacheStatus会从ttl变为miss,耗时随之上升。

运行效果解读与上手建议

启动后反复刷新页面,可以观察到一个清晰的对照实验:NONE卡片每次请求都回源数据库,耗时相对稳定;TTL卡片在 30 秒内刷新会命中缓存(绿色TTL CACHE HIT),超时后首次刷新变为miss;SWR卡片在旧数据过期后仍能立即返回数据并后台刷新;BOTH卡片则在前 30 秒强缓存、30~60 秒间 SWR 的窗口内展示组合策略的行为。若配合 Prisma Studio 修改某条名言后立即刷新,还能直观验证"缓存期间数据不实时"这一特性。

针对不同业务场景,可以给出如下选型建议:

  • 数据几乎不变化、对延迟极度敏感的场景,优先使用TTL;
  • 允许短暂展示旧数据、希望响应始终快速的场景,使用SWR;
  • 需要兼顾强缓存与最终一致性的场景,使用TTL + SWR组合;
  • 数据实时性要求极高、或仅需连接池聚合时,使用NONE(cacheStrategy传undefined)。

此外,本仓库的 accelerate/nextjs-starter(其 lib/utils/query.ts 还额外提供了addQuote写入示例)、accelerate/astro-starter 等兄弟项目共享同一套withAccelerate+withAccelerateInfo用法,可作为跨框架迁移的参考。更底层的cacheStrategy选项完整说明,可查阅 Prisma Accelerate 官方缓存文档。

总结

通过这个 Nuxt.js Starter,可以一次性看清 Prisma Accelerate 缓存体系的完整链路:?cache=查询参数经 zod 白名单校验后,被映射为cacheStrategy查询选项;查询通过$extends(withAccelerate())扩展后的客户端执行,并由.withAccelerateInfo()带回cacheStatus、region、lastModified等诊断元数据;最终由前端卡片组件将ttl/swr/miss/none四种状态渲染为可视化的命中指标。对任何希望在生产项目中引入 Prisma Accelerate 缓存、又需要可量化对比收益的团队而言,这份示例都是值得直接复用的起点。

  • 示例工程

【免费下载链接】prisma-examples

🚀 Ready-to-run Prisma example projects

项目地址:https://gitcode.com/gh_mirrors/pr/prisma-examples
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询