你可以查看我们的教程了解如何从 Cloudflare 获取环境变量
在现有项目中使用 Drizzle 和 SQLite Durable Objects 快速开始
基本文件结构
这是项目的基本文件结构。在 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 i drizzle-orm dotenv
npm i -D drizzle-kit tsx
第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 是基于演员模型的按需扩缩计算原语。
# Durable Objects 可以长期存在。用于需要长时间运行“服务器”的场景,例如实时应用。
# 文档: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 配置文件
Drizzle 配置 - 由 Drizzle Kit 使用的配置文件,包含数据库连接、迁移文件夹和模式文件等信息。
在项目根目录创建 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',
});第4步 - 反向分析数据库模式
Drizzle Kit provides a CLI command to introspect your database and generate a schema file with migrations. The schema file contains all the information about your database tables, columns, relations, and indices.
For example, you have such table in your database:
CREATE TABLE `users_table` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` text NOT NULL,
`age` integer NOT NULL,
`email` text NOT NULL UNIQUE
);Pull your database schema:
npx drizzle-kit pull --initThe result of introspection will be a schema.ts file, meta folder with snapshots of your database schema,
sql file with the migration and relations.ts file for relational queries.
Here is an example of the generated schema.ts file:
// table schema generated by introspection
import {
sqliteTable,
uniqueIndex,
integer,
text,
} from "drizzle-orm/sqlite-core";
export const usersTable = sqliteTable(
"users_table",
{
id: integer().primaryKey({ autoIncrement: true }).notNull(),
name: text().notNull(),
age: integer().notNull(),
email: text().notNull(),
},
(table) => [
uniqueIndex("users_table_email_unique").on(table.email)
]
);Learn more about introspection in the documentation.
第5步 - 把代码转移到你的实际 schema 文件
We recommend transferring the generated code from drizzle/schema.ts and drizzle/relations.ts to the actual schema file. In this guide we transferred code to src/db/schema.ts. Generated files for schema and relations can be deleted. This way you can manage your schema in a more structured way.
├ 📂 drizzle
│ ├ 📂 20242409125510_premium_mister_fear
│ │ ├ 📜 snapshot.json
│ │ └ 📜 migration.sql
│ ├ 📜 relations.ts ────────┐
│ └ 📜 schema.ts ───────────┤
├ 📂 src │
│ ├ 📂 db │
│ │ ├ 📜 relations.ts <─────┤
│ │ └ 📜 schema.ts <────────┘
│ └ 📜 index.ts
└ …第6步 - 连接 Drizzle ORM 到数据库
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 });
}
}第7步 - 查询数据库
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 - 从客户端发送到 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);
/*
const users: {
id: number;
name: string;
age: number;
email: string;
phone: string | null;
}[]
*/
// 方案 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);
/*
const users: {
id: number;
name: string;
age: number;
email: string;
phone: string | null;
}[]
*/
return Response.json(users);
}
}第8步 - 运行 index.ts 文件
要运行任何 TypeScript 文件,你有几种选择,但我们这里先专注于一种:使用 tsx
你已经安装了 tsx,所以现在可以运行我们的查询了
运行 index.ts 脚本
npx tsx src/index.ts
我们建议使用 bun 来运行 TypeScript 文件。使用 bun,无论你的项目是配置为 CommonJS(CJS)、ECMAScript 模块(ESM)还是其他模块格式,这类脚本都可以顺利运行,无需额外设置或调整。
使用 bun 运行脚本,执行以下命令:
bun src/index.ts如果你还没有安装 bun,请查看 Bun 安装文档
第9步 - 更新你的表结构(可选)
If you want to update your table schema, you can do it in the schema.ts file. For example, let’s add a new column phone to the users_table:
// table schema generated by introspection
import {
sqliteTable,
uniqueIndex,
integer,
text,
} from "drizzle-orm/sqlite-core";
export const usersTable = sqliteTable(
"users_table",
{
id: integer().primaryKey({ autoIncrement: true }).notNull(),
name: text().notNull(),
age: integer().notNull(),
email: text().notNull(),
phone: text(),
},
(table) => [
uniqueIndex("users_table_email_unique").on(table.email)
]
);第10步 - 将更改应用到数据库(可选)
您可以使用 drizzle-kit push 命令直接将更改应用到数据库中。这是一种方便的方法,适合在本地开发环境中快速测试新的架构设计或修改,允许快速迭代而无需管理迁移文件:
npx drizzle-kit push在 文档 中了解更多关于 push 命令的信息。
或者,您可以使用 drizzle-kit generate 命令生成迁移文件,然后使用 drizzle-kit migrate 命令应用这些迁移:
生成迁移:
npx drizzle-kit generate应用迁移:
npx drizzle-kit migrate在 文档 中了解更多关于迁移流程的信息。
第11步 - 使用新字段查询数据库(可选)
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 - 从客户端发送到 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',
phone: '123-456-7890',
});
console.log('新增用户,获取数据库中的所有用户: ', users);
/*
const users: {
id: number;
name: string;
age: number;
email: string;
phone: string | null;
}[]
*/
// 方案 B - 较慢,但有时适用于调试。
// 也可以直接调用单个公开的 Drizzle 查询,
// 但每个查询都是一次 Durable Object 实例的往返请求。
await stub.insert({
name: 'John',
age: 30,
email: 'john@example.com',
phone: '123-456-7890',
});
console.log('新增用户!');
const users = await stub.select();
console.log('获取数据库中的所有用户: ', users);
/*
const users: {
id: number;
name: string;
age: number;
email: string;
phone: string | null;
}[]
*/
return Response.json(users);
}
}