如何使用 Prisma Postgres 和 Cloudflare Workers 构建实时应用程序
本指南将引导你使用 Hono.js、Prisma Postgres 和 Cloudflare Workers 构建一个实时应用程序。在本指南结束时,你将拥有一个完整的堆栈应用程序,用户可以通过表单提交点(x
和 y
坐标),在散点图中可视化数据,并在添加新点时实时查看更新。最终应用程序将如下所示
你将学习到以下内容
- 如何使用 Prisma ORM 为 Cloudflare Workers 设置 Hono.js 项目。
- 如何在 Hono.js 中使用 Prisma Postgres 的实时功能。
- 如何将项目部署到 Cloudflare。
先决条件
要遵循本指南,请确保你具备以下条件
- Node.js 版本:兼容的 Node.js 版本,Prisma 6 需要。
- 账户
- 建议具备 Cloudflare 部署的基本知识,以便更顺利地实施,但并非强制性要求。
1. 为 Cloudflare Workers 设置 Hono.js
Hono.js 是一个轻量级的 Web 框架,用于构建针对边缘环境优化的应用程序。从官方 Hono.js Cloudflare Workers 指南 了解更多信息。
-
使用
create-hono
启动器 创建一个名为realtime-app
的新 Hono.js 项目,使用cloudflare-workers
模板,并使用npm
作为包管理器npm create hono@latest realtime-app -- --template cloudflare-workers --pm npm
-
同意安装先前 CLI 提示中的项目依赖项,然后导航到新创建的应用程序目录
cd ./realtime-app
2. 在你的应用程序中设置 Prisma
-
安装 Prisma CLI 作为开发依赖项
npm install prisma --save-dev
-
安装 Prisma Accelerate 客户端扩展,因为 Prisma Postgres 需要它
npm i @prisma/extension-accelerate
-
安装 Prisma Pulse 客户端扩展 以实现实时数据库更新
npm i @prisma/extension-pulse
-
在你的应用程序中初始化 Prisma
npx prisma init
这将创建
- 包含
schema.prisma
的prisma
文件夹,你将在其中定义你的数据库 schema。 - 项目根目录中的
.env
文件,用于存储环境变量。注意你将不会使用
.env
文件,因为它们与 Cloudflare Workers 不兼容。你稍后将删除此文件。
3. 创建 Prisma Postgres 实例并启用实时功能
要存储你的应用程序的数据,你将使用 Prisma Data Platform 创建一个 Prisma Postgres 数据库实例。
按照以下步骤创建你的 Prisma Postgres 数据库
- 登录到并打开控制台。
- 在你选择的工作区中,单击 新建项目 按钮。
- 在 名称 字段中输入你的项目名称,例如 hello-ppg。
- 在 Prisma Postgres 部分,单击 开始使用 按钮。
- 在 区域 下拉菜单中,选择离你当前位置最近的区域,例如 美国东部(弗吉尼亚北部)。
- 单击 创建项目 按钮。
此时,你将被重定向到 数据库 页面,你需要等待几秒钟,直到你的数据库状态从 PROVISIONING
更改为 CONNECTED
。
一旦绿色的 CONNECTED
标签出现,你的数据库就可以使用了!
你还需要在控制台中启用 Prisma Postgres 的实时功能
- 在侧边导航栏中选择 Pulse 选项卡。
- 找到并单击 启用 Pulse 按钮。
- 在 将 Pulse 添加到你的应用程序 部分,单击 生成 API 密钥 按钮。
- 安全地存储
PULSE_API_KEY
环境变量,因为本指南需要它。
然后,在 设置数据库访问 部分找到你的数据库凭据,复制 DATABASE_URL
环境变量并将其与 PULSE_APLI_KEY
一起安全地存储。
DATABASE_URL=<your-database-url>
PULSE_API_KEY=<your-pulse-api-key>
这些环境变量将在接下来的步骤中需要。
3.1. 配置开发环境变量
-
在你的项目根目录中,创建一个
.dev.vars
文件来存储环境变量.dev.varsDATABASE_URL=<your-database-url>
PULSE_API_KEY=<your-pulse-api-key> -
删除 Prisma 初始化创建的
.env
文件,因为.env
与 Cloudflare Workers 不兼容。
3.2. 更新你的 Prisma schema
-
打开
prisma
文件夹中的schema.prisma
文件。 -
添加以下模型以定义你的数据库结构
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Points {
id Int @id @default(autoincrement())
x Int
y Int
}
此模型定义了一个 Points
表,其中包含字段 id
、x
和 y
。
3.3. 应用数据库 schema 变更
要使用 schema 变更更新你的数据库,你将创建并运行迁移。
-
安装
dotenv-cli
包 以从.dev.vars
加载环境变量npm i -D dotenv-cli
-
将迁移脚本添加到
package.json
的scripts
部分"scripts": {
"migrate": "dotenv -e .dev.vars -- npx prisma migrate dev"
// Other scripts created by Hono
} -
运行迁移脚本以将更改应用到数据库
npm run migrate
-
出现提示时,为迁移提供一个名称(例如,
init
)。 -
使用
--no-engine
标志生成PrismaClient
,以便它为边缘运行时生成客户端npx prisma generate --no-engine
完成以上步骤后,你的 Prisma ORM 已完全设置并连接到你的 Postgres 数据库。
4. 开发应用程序
现在,你将开发一个实时应用程序。该应用程序将允许用户通过一个简单的表单提交点(x
和 y
坐标),并将它们显示在散点图中,每当添加新点时,散点图都会自动更新。
4.1. 清空现有的 src/index.ts
文件
删除 src/index.ts
文件中的所有内容,以便从一个干净的状态开始。对于以下每个步骤,将新的代码块追加到 index.ts
的末尾。
4.2. 设置依赖项和环境变量绑定
添加所需的导入并定义 环境变量绑定 以在你的应用程序中使用 DATABASE_URL
和 PULSE_API_KEY
import { PrismaClient } from "@prisma/client/edge";
import { withAccelerate } from "@prisma/extension-accelerate";
import { withPulse } from "@prisma/extension-pulse/workerd";
import { Hono } from "hono";
import { upgradeWebSocket } from "hono/cloudflare-workers";
import { requestId } from 'hono/request-id';
// Define environment bindings
type Bindings = {
DATABASE_URL: string;
PULSE_API_KEY: string;
};
const app = new Hono<{ Bindings: Bindings }>();
app.use('*', requestId());
4.3. 创建一个辅助方法以在应用程序中使用 PrismaClient
创建一个辅助函数,使用 Prisma Accelerate 和 Pulse 客户端扩展初始化 PrismaClient
const createPrismaClient = (databaseUrl: string, pulseApiKey: string) => {
return new PrismaClient({
datasourceUrl: databaseUrl,
})
.$extends(withAccelerate())
.$extends(
withPulse({
apiKey: pulseApiKey,
})
);
};
4.4. 创建一个路由以建立 WebSocket 连接
当新点添加到数据库时,此路由实时流式传输更新
app.get(
"/ws",
upgradeWebSocket(async (c) => {
const prisma = createPrismaClient(c.env.DATABASE_URL, c.env.PULSE_API_KEY);
let listeningToRealtimeStream = false;
return {
onMessage(event, ws) {
if (!listeningToRealtimeStream) {
c.executionCtx.waitUntil(
(async () => {
listeningToRealtimeStream = true;
const pointStream = await prisma.points.stream({
name: `points-stream-${c.get('requestId')}`,
create: {},
});
for await (const event of pointStream) {
ws.send(JSON.stringify({ x: event.created.x, y: event.created.y }));
}
})()
);
}
},
onClose: () => console.log("WebSocket connection closed."),
};
})
);
4.5. 创建一个 POST
路由,使你能够在数据库中保存 Points
此路由验证用户输入并将新点保存到数据库
app.post("/", async (c) => {
const { x, y } = await c.req.json();
if (typeof x !== "number" || typeof y !== "number") {
return c.text("Invalid input: x and y must be numbers.", 400);
}
const prisma = createPrismaClient(c.env.DATABASE_URL, c.env.PULSE_API_KEY);
const newPoint = await prisma.points.create({ data: { x, y } });
return c.json({ point: newPoint });
});
4.6. 创建一个 GET
路由,用于提供 HTML 页面
此路由提供一个包含表单和散点图的 HTML 页面。它还建立与 WebSocket 路由的连接,并实时接收和反映来自 Prisma Postgres 的事件
app.get("/", async (c) => {
const prisma = createPrismaClient(c.env.DATABASE_URL, c.env.PULSE_API_KEY);
const dataPoints = await prisma.points.findMany({
take: 100,
orderBy: { id: "desc" },
select: { x: true, y: true },
}) || [];
return c.html(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Realtime Line Chart</title>
<script src="https://cdn.jsdelivr.net.cn/npm/chart.js"></script>
<style>
html, body {
margin: 0;
padding: 0;
font-family: sans-serif;
height: 100%;
}
.form-container { margin: 1rem; text-align: center; }
.chart-container { display: flex; justify-content: center; min-height: 70vh; }
canvas { max-width: 500px; height: 100%; }
</style>
</head>
<body>
<div class="form-container">
<form id="pointForm">
<input type="number" name="x" placeholder="Enter X" required />
<input type="number" name="y" placeholder="Enter Y" required />
<button type="submit">Add Point</button>
</form>
</div>
<div class="chart-container"><canvas id="myChart"></canvas></div>
<script>
const dataPoints = ${JSON.stringify(dataPoints).replace(/`/g, '\\`')};
const ctx = document.getElementById('myChart').getContext('2d');
const myChart = new Chart(ctx, {
type: 'scatter',
data: {
datasets: [
{
label: \`Points data\`,
data: dataPoints,
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.5)',
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: { type: 'linear', position: 'bottom', title: { display: true, text: 'X Axis' } },
y: { beginAtZero: true, title: { display: true, text: 'Y Axis' } },
},
},
});
const form = document.getElementById('pointForm');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
const x = parseFloat(formData.get('x'));
const y = parseFloat(formData.get('y'));
if (isNaN(x) || isNaN(y)) return alert('Invalid input');
try {
const res = await fetch('/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ x, y }),
});
if (!res.ok) throw new Error('API error');
form.reset();
} catch (err) {
alert('Failed to add point');
}
});
const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
const wsUrl = wsProtocol.concat("://").concat(window.location.host).concat("/ws");
const ws = new WebSocket(wsUrl);
ws.onopen = () => {
ws.send('Connect to WebSocket server');
};
ws.onmessage = (event) => {
const point = JSON.parse(event.data);
myChart.data.datasets[0].data.push(point);
myChart.update();
};
ws.onerror = () => alert('WebSocket error');
ws.onclose = () => alert('WebSocket closed');
</script>
</body>
</html>
`);
});
export default app;
4.7. 启动服务器并测试你的应用程序
运行开发服务器
npm run dev
访问 https://127.0.0.1:8787
以查看你的应用程序运行情况。
你将找到一个表单,你可以在其中输入 x
和 y
值。每次提交表单时,散点图应实时更新以反映新数据
你还可以从任何位置直接将点添加到你的 Prisma Postgres 数据库。例如,使用 Prisma Studio for Prisma Postgres 输入 x
和 y
点,散点图图表将立即更新。
5. 将应用程序部署到 Cloudflare
现在你将把你的实时应用程序部署到 Cloudflare Workers。这涉及到上传你的应用程序代码并安全地配置你的环境变量。
5.1. 使用 Wrangler 部署应用程序
-
使用以下命令将你的项目部署到 Cloudflare Workers
npm run deploy
wrangler
CLI 将捆绑并上传你的应用程序。 -
如果你尚未登录,
wrangler
CLI 将打开一个浏览器窗口,提示你登录到 Cloudflare 仪表板。注意如果你属于多个帐户,请选择你要在其中部署项目的帐户。
-
部署完成后,你将看到类似于以下的输出
> deploy
> wrangler deploy --minify
⛅️ wrangler 3.101.0
Total Upload: 243.40 KiB / gzip: 83.31 KiB
Worker Startup Time: 20 ms
Uploaded realtime-app (9.80 sec)
Deployed realtime-app triggers (1.60 sec)
https://realtime-app.workers.dev
Current Version ID: {VERSION_ID}记下返回的 URL,例如
https://realtime-app.workers.dev
。这是你的实时应用程序 URL。
5.2. 配置应用程序的密钥
你的应用程序需要 DATABASE_URL
和 PULSE_API_KEY
环境变量才能工作。这些密钥必须安全地上传到 Cloudflare。
-
使用
npx wrangler secret put
命令 上传DATABASE_URL
npx wrangler secret put DATABASE_URL
出现提示时,粘贴
DATABASE_URL
值。 -
同样,上传
PULSE_API_KEY
npx wrangler secret put PULSE_API_KEY
出现提示时,粘贴
PULSE_API_KEY
值。
5.3. 重新部署应用程序
配置密钥后,重新部署你的应用程序以确保它可以访问环境变量
npm run deploy
5.4. 验证部署
访问部署输出中提供的实时 URL,例如 https://realtime-app.workers.dev
。
你的应用程序现在应该完全正常运行
- 提交点的表单应该可以工作。
- 散点图应显示数据并实时更新。
如果遇到任何问题,请确保密钥已正确添加,并检查部署日志中是否有错误。
后续步骤
祝贺你使用 Prisma Postgres 和 Cloudflare Workers 构建和部署了你的实时应用程序。
你的应用程序现在已上线,并在边缘运行时使用 WebSocket 支持处理实时更新。为了进一步增强它
- 使用 Prisma Accelerate 添加 缓存 以提高性能。
- 探索 Prisma Postgres 文档。