Astro + drizzle ORM + Cloudflare worker + D1 使用指南
- Astro
- Drizzle
- Cloudflare worker
- D1
- 04 Feb, 2026
在Astro + drizzle ORM + Cloudflare worker + D1 技术栈使用时,我被开发环境和 cloudflare worker 环境使用 D1 数据库 搞的晕头转向,这里记录一下。
drizzle.config.ts 配置
import { defineConfig } from 'drizzle-kit';
import fs from 'node:fs';
import path from 'node:path';
export default defineConfig({
out: './src/db/migrations',
schema: './src/db/schema.ts',
dialect: 'sqlite',
driver: 'd1-http',
dbCredentials: {
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
databaseId: process.env.CLOUDFLARE_DATABASE_ID!,
token: process.env.CLOUDFLARE_D1_TOKEN!,
},
});
如果你想使用drizzle-kit generate 、migrate、studio等命令,就必须使用 d1-http 库链接
注意:需要在环境变量中配置 CLOUDFLARE_ACCOUNT_ID、CLOUDFLARE_DATABASE_ID 和 CLOUDFLARE_D1_TOKEN。
src/db/index.ts代码
这里是最麻烦的,你要考虑到 Cloudflare Worker 的运行环境和本地开发环境的差异。
Cloudflare Worker是需要直接使用 DBindings 来访问 D1 数据库的,而本地开发环境则需要使用 HTTP 接口来访问 D1 数据库。
import { drizzle } from 'drizzle-orm/d1';
import * as schema from './schema';
import { getEnv } from '@/lib/env';
export type DbType = ReturnType<typeof drizzle<typeof schema>>;
let cachedDb: DbType | null = null;
let cachedBinding: any = null;
class D1RemoteBinding {
constructor(
private accountId: string,
private databaseId: string,
private apiToken: string
) {}
prepare(sql: string) {
const execute = async (params: any[] = []) => {
const url = `https://api.cloudflare.com/client/v4/accounts/${this.accountId}/d1/database/${this.databaseId}/query`;
const response = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${this.apiToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ sql, params }),
});
const data = await response.json();
if (!data.success) {
throw new Error(`D1 远程查询失败: ${JSON.stringify(data.errors)}`);
}
// console.log('D1 API Response:', JSON.stringify(data, null, 2));
return data.result[0];
};
const statement = {
bind: (...params: any[]) => ({
// all() 需要返回 { results: [...] } 格式
all: () => execute(params),
get: () => execute(params).then((res) => res.results[0]),
run: () => execute(params),
// values() 应返回二维数组,每行是值数组(按列顺序)
values: () => execute(params).then((res) => res.results.map((row: Record<string, any>) => Object.values(row))),
first: () => execute(params).then((res) => res.results[0]),
// raw() 应返回二维数组格式,这是 Drizzle ORM 用于字段映射的核心方法
raw: () => execute(params).then((res) => res.results.map((row: Record<string, any>) => Object.values(row))),
}),
// 无参数版本
all: () => execute([]),
get: () => execute([]).then((res) => res.results[0]),
run: () => execute([]),
values: () => execute([]).then((res) => res.results.map((row: Record<string, any>) => Object.values(row))),
first: () => execute([]).then((res) => res.results[0]),
raw: () => execute([]).then((res) => res.results.map((row: Record<string, any>) => Object.values(row))),
};
return statement;
}
async batch(statements: any[]) {
// 简单实现 batch
return Promise.all(statements.map((s) => s.run()));
}
async exec(sql: string) {
return this.prepare(sql).run();
}
}
export async function getDb(): Promise<DbType> {
if (cachedDb && cachedBinding) {
return cachedDb;
}
const globalBinding = (globalThis as any).__D1_BINDING__;
if (globalBinding) {
cachedBinding = globalBinding;
cachedDb = drizzle(globalBinding, { schema });
return cachedDb;
}
if (typeof process !== 'undefined') {
const { config } = await import('dotenv');
config();
}
const accountId = getEnv('CLOUDFLARE_ACCOUNT_ID');
const databaseId = getEnv('CLOUDFLARE_DATABASE_ID');
const apiToken = getEnv('CLOUDFLARE_D1_TOKEN');
if (accountId && databaseId && apiToken) {
try {
const remoteBinding = new D1RemoteBinding(accountId, databaseId, apiToken);
cachedBinding = remoteBinding;
cachedDb = drizzle(remoteBinding as any, { schema });
console.log('🔗 已通过 Cloudflare API 连接到生产环境 D1 数据库');
return cachedDb;
} catch (error) {
console.error('无法初始化远程 D1 连接:', error);
}
}
throw new Error(
'D1 数据库连接失败。\n' +
'请确保在 .env 中正确配置了生产环境的 D1 凭证:\n' +
'- CLOUDFLARE_ACCOUNT_ID\n' +
'- CLOUDFLARE_DATABASE_ID\n' +
'- CLOUDFLARE_D1_TOKEN'
);
}
export function setGlobalD1Binding(binding: any) {
(globalThis as any).__D1_BINDING__ = binding;
cachedBinding = binding;
cachedDb = drizzle(binding, { schema });
}
本地开发环境可以自己实现一个D1RemoteBinding 或者使用第三方 proxy:例如 @nerdfolio/drizzle-d1-proxy 这类库提供运行时 HTTP 代理访问 D1。
那么这里能不能直接使用 d1-http 库替换 D1RemoteBinding 呢?
答案是不能。这里不能“直接用 d1-http 库替换”而不改代码。d1-http 是 Drizzle Kit 的 CLI 连接驱动(用于 migrate/push/studio 等),不是 drizzle-orm 运行时公开的 DB 适配器,所以它不适合在你的应用运行时直接替代 D1RemoteBinding。(orm.drizzle.team)
更具体一点:
d1-http 的官方定位是 Drizzle Kit(迁移/管理工具)。 driver: ‘d1-http’ 是给 drizzle-kit 用的,不是给应用运行时 drizzle() 用的。
应用运行时在 Cloudflare 上的标准做法:
- 正式做法是直接用 D1 binding:drizzle(env.DB),即 Worker 运行时的 D1 绑定。(orm.drizzle.team)
- D1RemoteBinding 是为本地/非 Worker 环境用 API Token 访问 D1 的“替代方案”。