Cloudflare
操作指南
数据库与ORM

本页将向您展示如何在 OpenNext 中设置一些流行的数据库 ORM 库。在 Cloudflare Workers 中使用这些库时需要注意一些细节,我们将在此进行说明。

如果您遇到特定库的问题,请在 OpenNext GitHub 仓库 (opens in a new tab) 提交 issue。

Drizzle ORM

Drizzle (opens in a new tab) 是一个面向 SQL 数据库的 TypeScript ORM。它设计轻量且易于使用,非常适合 Cloudflare Workers。 Drizzle 不需要太多特殊配置,但有一点非常重要:不要使用全局客户端。

lib/db.ts

您应该为每个请求创建新的客户端,而不是使用全局客户端。这是因为某些适配器(如 Postgres)会使用连接池,并为多个请求复用同一个连接。这在 Cloudflare Workers 中是不允许的,会导致后续请求失败。

PostgreSQL

不要这样做:

//lib/db.ts
import { drizzle } from "drizzle-orm/node-postgres";
import * as schema from "./schema/pg";
import { Pool } from "pg";
 
const pool = new Pool({
  connectionString: process.env.PG_URL,
});
 
export const db = drizzle({ client: pool, schema });

而应该这样做:

//lib/db.ts
import { drizzle } from "drizzle-orm/node-postgres";
// 您可以使用 react 的 cache 来在同一请求期间缓存客户端
// 这不是强制性的,且仅对服务器组件有效
import { cache } from "react";
import * as schema from "./schema/pg";
import { Pool } from "pg";
 
export const getDb = cache(() => {
  const pool = new Pool({
    connectionString: process.env.PG_URL,
    // 您不希望为多个请求复用同一个连接
    maxUses: 1,
  });
  return drizzle({ client: pool, schema });
});

D1 示例

import { getCloudflareContext } from "@opennextjs/cloudflare";
import { drizzle } from "drizzle-orm/d1";
import { cache } from "react";
import * as schema from "./schema/d1";
 
export const getDb = cache(() => {
  const { env } = getCloudflareContext();
  return drizzle(env.MY_D1, { schema });
});
 
// 这个函数用于静态路由(如 ISR/SSG)
export const getDbAsync = cache(async () => {
  const { env } = await getCloudflareContext({ async: true });
  return drizzle(env.MY_D1, { schema });
});

Hyperdrive 示例

import { getCloudflareContext } from "@opennextjs/cloudflare";
import { drizzle } from "drizzle-orm/node-postgres";
import { cache } from "react";
import * as schema from "./schema/pg";
import { Pool } from "pg";
 
export const getDb = cache(() => {
  const { env } = getCloudflareContext();
  const connectionString = env.HYPERDRIVE.connectionString;
  const pool = new Pool({
    connectionString: process.env.PG_URL,
    // 不建议为多个请求复用同一个连接
    maxUses: 1,
  });
  return drizzle({ client: pool, schema });
});
 
// 这个函数用于静态路由(如 ISR/SSG)
export const getDbAsync = cache(async () => {
  const { env } = await getCloudflareContext({ async: true });
  const connectionString = env.HYPERDRIVE.connectionString;
  const pool = new Pool({
    connectionString: process.env.PG_URL,
    // 不建议为多个请求复用同一个连接
    maxUses: 1,
  });
  return drizzle({ client: pool, schema });
});

你可以使用 getDb 函数为每个请求获取一个新的客户端。这将确保你不会遇到连接池相关的问题。

Prisma ORM

Prisma (opens in a new tab) 是一个流行的 Node.js 和 TypeScript ORM(对象关系映射)工具。它设计简洁易用,并提供了开箱即用的丰富功能。不过在 Cloudflare Workers 中使用 Prisma 时需要注意一些细节。

schema.prisma

在 OpenNext 中使用 Prisma 时,不需要为生成的客户端指定输出目录。

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["driverAdapters"]
}

这是因为生成的客户端需要由 OpenNext 进行修补才能与 Cloudflare Workers 兼容。如果指定了输出目录,OpenNext 将无法修补客户端,导致其无法正常工作。

next.config.ts

由于 Prisma 对 Cloudflare Workers 有特定的导出配置,你需要在 next.config.ts 文件中添加以下内容:

const nextConfig: NextConfig = {
  serverExternalPackages: ["@prisma/client", ".prisma/client"],
};

这样做可以确保生成的客户端和 Prisma 客户端都被包含在 workerd 运行时的构建中。

lib/db.ts

应该为每个请求创建新的客户端实例,而不是使用全局客户端。这是因为某些适配器(如 Postgres)会使用连接池,并在多个请求间复用同一个连接。这在 Cloudflare Workers 中是不允许的,会导致后续请求失败。

D1 示例

不要这样做:

//lib/db.ts
import { getCloudflareContext } from "@opennextjs/cloudflare";
import { PrismaClient } from "@prisma/client";
import { PrismaD1 } from "@prisma/adapter-d1";
 
const { env } = getCloudflareContext();
const adapter = new PrismaD1(env.MY_D1);
export const db = new PrismaClient();

而应该这样做:

//lib/db.ts
import { getCloudflareContext } from "@opennextjs/cloudflare";
// 可以使用 react 的 cache 来在同一个请求期间缓存客户端
// 这不是强制性的,且仅对服务器组件有效
import { cache } from "react";
import { PrismaClient } from "@prisma/client";
import { PrismaD1 } from "@prisma/adapter-d1";
 
export const getDb = cache(() => {
  const { env } = getCloudflareContext();
  const adapter = new PrismaD1(env.MY_D1);
  return new PrismaClient({ adapter });
});
 
// 如果需要在静态路由(如 ISR/SSG)中访问 `getCloudflareContext`,应该使用异步版本的 `getCloudflareContext` 来获取上下文
export const getDbAsync = async () => {
  const { env } = await getCloudflareContext({ async: true });
  const adapter = new PrismaD1(env.MY_D1);
  const prisma = new PrismaClient({ adapter });
  return prisma;
};

然后你可以使用 getDb 函数为每个请求获取一个新的客户端。这将确保你不会遇到连接池相关的任何问题。

PostgreSQL

你也可以将 Prisma 与 PostgreSQL 配合使用。配置方式与上述 D1 的设置类似,但需要使用 PrismaPostgres 适配器而非 PrismaD1 适配器。

import { cache } from "react";
import { PrismaClient } from "@prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
 
export const getDb = cache(() => {
  const connectionString = process.env.PG_URL ?? "";
  const adapter = new PrismaPg({ connectionString, maxUses: 1 });
  const prisma = new PrismaClient({ adapter });
  return prisma;
});

之后你可以使用 getDb 函数为每个请求获取一个新的客户端。这将确保你不会遇到任何连接池相关的问题。

Hyperdrive

你也可以将 Prisma 与 Hyperdrive 配合使用。配置方式与上述 PostgreSQL 的设置类似。

//lib/db.ts
import { getCloudflareContext } from "@opennextjs/cloudflare";
// 你可以使用 react 的 cache 来在同一请求期间缓存客户端
// 这不是强制性的,且仅对服务器组件有效
import { cache } from "react";
import { PrismaClient } from "@prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
 
export const getDb = cache(() => {
  const { env } = getCloudflareContext();
  const connectionString = env.HYPERDRIVE.connectionString;
  const adapter = new PrismaPg({ connectionString, maxUses: 1 });
  return new PrismaClient({ adapter });
});
 
// 这个函数用于静态路由(即 ISR/SSG)
export const getDbAsync = async () => {
  const { env } = await getCloudflareContext({ async: true });
  const connectionString = env.HYPERDRIVE.connectionString;
  const adapter = new PrismaPg({ connectionString, maxUses: 1 });
  return new PrismaClient({ adapter });
};