使用 Drizzle 和 SQLite Durable Objects 快速入门

This guide assumes familiarity with:
  • dotenv - 管理环境变量的包 - 阅读这里
  • tsx - 运行 TypeScript 文件的包 - 阅读这里
  • Cloudflare SQLite Durable Objects - 嵌入于 Durable Object 中的 SQLite 数据库 - 阅读这里
  • wrangler - Cloudflare 开发者平台命令行接口 - 阅读这里

基本文件结构

这是项目的基本文件结构。在 src/db 目录中,我们在 schema.ts 中有表定义。在 drizzle 文件夹中,有 SQL 迁移文件和快照。

📦 <project root>
 ├ 📂 drizzle
 ├ 📂 src
 │   ├ 📂 db
 │   │  └ 📜 schema.ts
 │   └ 📜 index.ts
 ├ 📜 .env
 ├ 📜 drizzle.config.ts
 ├ 📜 package.json
 └ 📜 tsconfig.json

第 1 步 - 安装必需的包

npm
yarn
pnpm
bun
npm i drizzle-orm dotenv
npm i -D drizzle-kit wrangler @cloudflare/workers-types

第 2 步 - 设置 wrangler.toml

你需要有一个用于 D1 数据库的 wrangler.toml 文件,内容大致如下:

#:schema node_modules/wrangler/config-schema.json
name = "sqlite-durable-objects"
main = "src/index.ts"
compatibility_date = "2024-11-12"
compatibility_flags = [ "nodejs_compat" ]

# 绑定一个 Durable Object。Durable Object 是基于 actor 模型的按需伸缩计算原语。
# Durable Object 可以持续存在。适用于需要长时间运行“服务器”的场景,如实时应用。
# 文档:https://developers.cloudflare.com/workers/wrangler/configuration/#durable-objects
[[durable_objects.bindings]]
name = "MY_DURABLE_OBJECT"
class_name = "MyDurableObject"

# Durable Object 迁移。
# 文档:https://developers.cloudflare.com/workers/wrangler/configuration/#migrations
[[migrations]]
tag = "v1"
new_sqlite_classes = ["MyDurableObject"]

# 需要规则以便在后续步骤中导入迁移
[[rules]] 
type = "Text"
globs = ["**/*.sql"]
fallthrough = true

第 3 步 - 连接 Drizzle ORM 到数据库

/// <reference types="@cloudflare/workers-types" />
import { drizzle, type DrizzleSqliteDODatabase } from 'drizzle-orm/durable-sqlite';
import { DurableObject } from 'cloudflare:workers'

export class MyDurableObject extends DurableObject {
	storage: DurableObjectStorage;
	db: DrizzleSqliteDODatabase;

	constructor(ctx: DurableObjectState, env: Env) {
		super(ctx, env);
		this.storage = ctx.storage;
		this.db = drizzle(this.storage, { logger: false });
	}
}

第 4 步 - 生成 wrangler 类型

npm
yarn
pnpm
bun
npx wrangler types

该命令会输出一个 worker-configuration.d.ts 文件。

第 5 步 - 创建一个表

Create a schema.ts file in the src/db directory and declare your table:

src/db/schema.ts
import { int, sqliteTable, text } from "drizzle-orm/sqlite-core";

export const usersTable = sqliteTable("users_table", {
  id: int().primaryKey({ autoIncrement: true }),
  name: text().notNull(),
  age: int().notNull(),
  email: text().notNull().unique(),
});

第 6 步 - 设置 Drizzle 配置文件

Drizzle 配置 是一个由 Drizzle Kit 使用的配置文件,包含有关数据库连接、迁移文件夹和模式文件的所有信息。

在项目根目录下创建 drizzle.config.ts 文件,并添加以下内容:

drizzle.config.ts
import 'dotenv/config';
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  out: './drizzle',
  schema: './src/db/schema.ts',
  dialect: 'sqlite',
  driver: 'durable-sqlite',
});

第 7 步 - 应用数据库变更

生成迁移:

npx drizzle-kit generate

迁移只能从 Cloudflare Workers 应用。 为此,我们在 MyDurableObject 中定义迁移功能:

/// <reference types="@cloudflare/workers-types" />
import { drizzle, type DrizzleSqliteDODatabase } from 'drizzle-orm/durable-sqlite';
import { DurableObject } from 'cloudflare:workers'
import { migrate } from 'drizzle-orm/durable-sqlite/migrator';
import migrations from '../drizzle/migrations';

export class MyDurableObject extends DurableObject {
	storage: DurableObjectStorage;
	db: DrizzleSqliteDODatabase;

	constructor(ctx: DurableObjectState, env: Env) {
		super(ctx, env);
		this.storage = ctx.storage;
		this.db = drizzle(this.storage, { logger: false });
	}

	async migrate() {
		migrate(this.db, migrations);
	}
}

第 8 步 - 迁移并查询数据库

/// <reference types="@cloudflare/workers-types" />
import { drizzle, DrizzleSqliteDODatabase } from 'drizzle-orm/durable-sqlite';
import { DurableObject } from 'cloudflare:workers'
import { migrate } from 'drizzle-orm/durable-sqlite/migrator';
import migrations from '../drizzle/migrations';
import { usersTable } from './db/schema';

export class MyDurableObject extends DurableObject {
	storage: DurableObjectStorage;
	db: DrizzleSqliteDODatabase<any>;

	constructor(ctx: DurableObjectState, env: Env) {
		super(ctx, env);
		this.storage = ctx.storage;
		this.db = drizzle(this.storage, { logger: false });

		// 确保所有迁移在接受查询前完成。
		// 否则你需要在任何访问 Drizzle 数据库 this.db 的函数中调用 `this.migrate()`。
		ctx.blockConcurrencyWhile(async () => {
			await this._migrate();
		});
	}

	async insertAndList(user: typeof usersTable.$inferInsert) {
		await this.insert(user);
		return this.select();
	}

	async insert(user: typeof usersTable.$inferInsert) {
		await this.db.insert(usersTable).values(user);
	}

	async select() {
		return this.db.select().from(usersTable);
	}

	async _migrate() {
		migrate(this.db, migrations);
	}
}

export default {
	/**
	 * 这是 Cloudflare Worker 的标准 fetch 处理器
	 *
	 * @param request - Client 发送到 Worker 的请求
	 * @param env - 用于引用 wrangler.toml 中声明绑定的接口
	 * @param ctx - Worker 的执行上下文
	 * @returns 返回给客户端的响应
	 */
	async fetch(request: Request, env: Env): Promise<Response> {
		const id: DurableObjectId = env.MY_DURABLE_OBJECT.idFromName('durable-object');
		const stub = env.MY_DURABLE_OBJECT.get(id);

		// 选项 A - 最大性能。
		// 优先将所有数据库交互封装在一次 Durable Object 调用中
		// 以获得最大性能,因为 DO 内的数据库访问非常快。
		const usersAll = await stub.insertAndList({
			name: 'John',
			age: 30,
			email: 'john@example.com',
		});
		console.log('新用户已创建。正在从数据库获取所有用户:', users);

		// 选项 B - 慢速,但在某些调试场景下可能有用。
		// 你也可以直接调用单个 Drizzle 查询(如果暴露了的话),
		// 但请记住每次查询都是对 Durable Object 实例的一次往返。
		await stub.insert({
			name: 'John',
			age: 30,
			email: 'john@example.com',
		});
		console.log('新用户已创建!');
	
		const users = await stub.select();
		console.log('正在从数据库获取所有用户:', users);

		return Response.json(users);
	}
}