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,
    // 您不希望为多个请求重用相同的连接
    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,
    // 您不希望为多个请求重用相同的连接
    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 });
};