CloudflareD1技能 cloudflare-d1

Cloudflare D1是为Cloudflare Workers设计的无服务器SQLite数据库,具有全球分发和零冷启动的特点,通过Drizzle ORM实现类型安全的查询和数据迁移。

Serverless 0 次安装 0 次浏览 更新于 3/4/2026

name: cloudflare-d1 description: Cloudflare D1 SQLite数据库,包含Workers、Drizzle ORM和迁移

Cloudflare D1 技能

加载方式:base.md + typescript.md

Cloudflare D1是为Cloudflare Workers设计的无服务器SQLite数据库,具有全球分发和零冷启动的特点。

资料来源: D1文档 | Drizzle + D1 | Wrangler CLI


核心原则

在边缘使用SQLite,迁移在版本控制中,Drizzle用于类型安全。

D1将SQLite的简单性带入无服务器环境。设计时考虑水平扩展(多个小型数据库)而不是垂直扩展(一个大型数据库)。使用Drizzle ORM进行类型安全的查询和迁移。


D1技术栈

组件 目的
D1 无服务器SQLite数据库
Workers 应用程序的边缘运行时
Wrangler 开发和部署的CLI
Drizzle ORM 带迁移的类型安全ORM
Drizzle Kit 迁移工具
Hono 轻量级Web框架(可选)

项目设置

创建Worker项目

# 创建新项目
npm create cloudflare@latest my-app -- --template "worker-typescript"
cd my-app

# 安装依赖
npm install drizzle-orm
npm install -D drizzle-kit

创建D1数据库

# 创建数据库(同时创建本地和远程)
npx wrangler d1 create my-database

# 输出:
# [[d1_databases]]
# binding = "DB"
# database_name = "my-database"
# database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

配置wrangler.toml

name = "my-app"
main = "src/index.ts"
compatibility_date = "2024-01-01"

[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
migrations_dir = "drizzle"
migrations_table = "drizzle_migrations"

生成TypeScript类型

# 从wrangler.toml生成环境类型
npx wrangler types

# 创建worker-configuration.d.ts:
# interface Env {
#   DB: D1Database;
# }

Drizzle ORM设置

模式定义

// src/db/schema.ts
import { sqliteTable, text, integer, real, blob } from 'drizzle-orm/sqlite-core';
import { sql } from 'drizzle-orm';

export const users = sqliteTable('users', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  email: text('email').notNull().unique(),
  name: text('name').notNull(),
  role: text('role', { enum: ['user', 'admin'] }).default('user'),
  createdAt: text('created_at').default(sql`CURRENT_TIMESTAMP`),
  updatedAt: text('updated_at').default(sql`CURRENT_TIMESTAMP`)
});

export const posts = sqliteTable('posts', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  title: text('title').notNull(),
  content: text('content'),
  authorId: integer('author_id').references(() => users.id),
  published: integer('published', { mode: 'boolean' }).default(false),
  viewCount: integer('view_count').default(0),
  createdAt: text('created_at').default(sql`CURRENT_TIMESTAMP`)
});

export const tags = sqliteTable('tags', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  name: text('name').notNull().unique()
});

export const postTags = sqliteTable('post_tags', {
  postId: integer('post_id').references(() => posts.id),
  tagId: integer('tag_id').references(() => tags.id)
});

// 类型导出
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
export type Post = typeof posts.$inferSelect;
export type NewPost = typeof posts.$inferInsert;

Drizzle配置

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

export default defineConfig({
  schema: './src/db/schema.ts',
  out: './drizzle',
  dialect: 'sqlite',
  driver: 'd1-http',
  dbCredentials: {
    accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
    databaseId: process.env.CLOUDFLARE_DATABASE_ID!,
    token: process.env.CLOUDFLARE_D1_TOKEN!
  }
});

数据库客户端

// src/db/index.ts
import { drizzle } from 'drizzle-orm/d1';
import * as schema from './schema';

export function createDb(d1: D1Database) {
  return drizzle(d1, { schema });
}

export type Database = ReturnType<typeof createDb>;
export * from './schema';

迁移工作流

生成迁移

# 从模式变化生成迁移
npx drizzle-kit generate

# 输出:drizzle/0000_initial.sql

本地应用迁移

# 应用到本地D1
npx wrangler d1 migrations apply my-database --local

# 或者通过Drizzle
npx drizzle-kit migrate

应用迁移到生产环境

# 应用到远程D1
npx wrangler d1 migrations apply my-database --remote

# 预览(dry run)
npx wrangler d1 migrations apply my-database --remote --dry-run

迁移文件示例

-- drizzle/0000_initial.sql
CREATE TABLE `users` (
  `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
  `email` text NOT NULL,
  `name` text NOT NULL,
  `role` text DEFAULT 'user',
  `created_at` text DEFAULT CURRENT_TIMESTAMP,
  `updated_at` text DEFAULT CURRENT_TIMESTAMP
);

CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);

CREATE TABLE `posts` (
  `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
  `title` text NOT NULL,
  `content` text,
  `author_id` integer REFERENCES `users`(`id`),
  `published` integer DEFAULT false,
  `view_count` integer DEFAULT 0,
  `created_at` text DEFAULT CURRENT_TIMESTAMP
);

Worker实现

基本Worker与Hono

// src/index.ts
import { Hono } from 'hono';
import { createDb, users, posts } from './db';
import { eq, desc } from 'drizzle-orm';

type Bindings = {
  DB: D1Database;
};

const app = new Hono<{ Bindings: Bindings }>();

// 中间件注入db
app.use('*', async (c, next) => {
  c.set('db', createDb(c.env.DB));
  await next();
});

// 列出用户
app.get('/users', async (c) => {
  const db = c.get('db');
  const allUsers = await db.select().from(users);
  return c.json(allUsers);
});

// 通过ID获取用户
app.get('/users/:id', async (c) => {
  const db = c.get('db');
  const id = parseInt(c.req.param('id'));

  const user = await db.select().from(users).where(eq(users.id, id)).get();

  if (!user) {
    return c.json({ error: '用户未找到' }, 404);
  }
  return c.json(user);
});

// 创建用户
app.post('/users', async (c) => {
  const db = c.get('db');
  const body = await c.req.json<{ email: string; name: string }>();

  const result = await db.insert(users).values({
    email: body.email,
    name: body.name
  }).returning();

  return c.json(result[0], 201);
});

// 更新用户
app.put('/users/:id', async (c) => {
  const db = c.get('db');
  const id = parseInt(c.req.param('id'));
  const body = await c.req.json<Partial<{ email: string; name: string }>>();

  const result = await db.update(users)
    .set({ ...body, updatedAt: new Date().toISOString() })
    .where(eq(users.id, id))
    .returning();

  if (result.length === 0) {
    return c.json({ error: '用户未找到' }, 404);
  }
  return c.json(result[0]);
});

// 删除用户
app.delete('/users/:id', async (c) => {
  const db = c.get('db');
  const id = parseInt(c.req.param('id'));

  const result = await db.delete(users).where(eq(users.id, id)).returning();

  if (result.length === 0) {
    return c.json({ error: '用户未找到' }, 404);
  }
  return c.json({ deleted: true });
});

export default app;

原始D1 API(无ORM)

// src/index.ts
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === '/users' && request.method === 'GET') {
      const { results } = await env.DB.prepare(
        'SELECT * FROM users ORDER BY created_at DESC'
      ).all();
      return Response.json(results);
    }

    if (url.pathname === '/users' && request.method === 'POST') {
      const body = await request.json() as { email: string; name: string };

      const result = await env.DB.prepare(
        'INSERT INTO users (email, name) VALUES (?, ?) RETURNING *'
      ).bind(body.email, body.name).first();

      return Response.json(result, { status: 201 });
    }

    return new Response('Not Found', { status: 404 });
  }
};

查询模式

选择查询

import { eq, and, or, like, gt, desc, asc, count, sql } from 'drizzle-orm';

// 基本选择
const allPosts = await db.select().from(posts);

// 选择特定列
const titles = await db.select({ id: posts.id, title: posts.title }).from(posts);

// 条件语句
const published = await db.select().from(posts).where(eq(posts.published, true));

// 多个条件
const recentPublished = await db.select().from(posts).where(
  and(
    eq(posts.published, true),
    gt(posts.createdAt, '2024-01-01')
  )
);

// OR条件
const featured = await db.select().from(posts).where(
  or(
    eq(posts.viewCount, 1000),
    like(posts.title, '%featured%')
  )
);

// 排序和限制
const topPosts = await db.select()
  .from(posts)
  .orderBy(desc(posts.viewCount))
  .limit(10);

// 分页
const page2 = await db.select()
  .from(posts)
  .orderBy(desc(posts.createdAt))
  .limit(10)
  .offset(10);

// 计数
const postCount = await db.select({ count: count() }).from(posts);

连接

// 内连接
const postsWithAuthors = await db.select({
  post: posts,
  author: users
})
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id));

// 左连接
const allPostsWithAuthors = await db.select()
  .from(posts)
  .leftJoin(users, eq(posts.authorId, users.id));

// 通过连接表的多对多
const postsWithTags = await db.select({
  post: posts,
  tag: tags
})
.from(posts)
.leftJoin(postTags, eq(posts.id, postTags.postId))
.leftJoin(tags, eq(postTags.tagId, tags.id));

插入、更新、删除

// 单个插入
const newUser = await db.insert(users).values({
  email: 'user@example.com',
  name: 'John Doe'
}).returning();

// 批量插入
await db.insert(users).values([
  { email: 'a@test.com', name: 'Alice' },
  { email: 'b@test.com', name: 'Bob' }
]);

// Upsert(冲突时插入或更新)
await db.insert(users)
  .values({ email: 'user@test.com', name: 'New Name' })
  .onConflictDoUpdate({
    target: users.email,
    set: { name: 'New Name' }
  });

// 更新
await db.update(posts)
  .set({ published: true })
  .where(eq(posts.id, 1));

// 更新并增加
await db.update(posts)
  .set({ viewCount: sql`${posts.viewCount} + 1` })
  .where(eq(posts.id, 1));

// 删除
await db.delete(posts).where(eq(posts.id, 1));

事务

// D1通过批量支持事务
const results = await db.batch([
  db.insert(users).values({ email: 'a@test.com', name: 'A' }),
  db.insert(users).values({ email: 'b@test.com', name: 'B' }),
  db.update(posts).set({ published: true }).where(eq(posts.id, 1))
]);

// 原始D1批量
const batchResults = await env.DB.batch([
  env.DB.prepare('INSERT INTO users (email, name) VALUES (?, ?)').bind('a@test.com', 'A'),
  env.DB.prepare('INSERT INTO users (email, name) VALUES (?, ?)').bind('b@test.com', 'B')
]);

本地开发

启动开发服务器

# 本地开发使用D1
npx wrangler dev

# 使用特定端口
npx wrangler dev --port 8787

数据库管理

# 本地执行SQL
npx wrangler d1 execute my-database --local --command "SELECT * FROM users"

# 执行SQL文件
npx wrangler d1 execute my-database --local --file ./seed.sql

# 打开SQLite shell
npx wrangler d1 execute my-database --local --command ".tables"

Drizzle Studio

# 运行Drizzle Studio进行可视化数据库管理
npx drizzle-kit studio

种子数据

-- seed.sql
INSERT INTO users (email, name, role) VALUES
  ('admin@example.com', 'Admin User', 'admin'),
  ('user@example.com', 'Test User', 'user');

INSERT INTO posts (title, content, author_id, published) VALUES
  ('First Post', 'Hello World!', 1, true),
  ('Draft Post', 'Work in progress...', 1, false);
# 本地数据库播种
npx wrangler d1 execute my-database --local --file ./seed.sql

多环境设置

wrangler.toml

name = "my-app"
main = "src/index.ts"
compatibility_date = "2024-01-01"

# 开发环境
[env.dev]
[[env.dev.d1_databases]]
binding = "DB"
database_name = "my-database-dev"
database_id = "dev-database-id"

# 暂存环境
[env.staging]
[[env.staging.d1_databases]]
binding = "DB"
database_name = "my-database-staging"
database_id = "staging-database-id"

# 生产环境
[env.production]
[[env.production.d1_databases]]
binding = "DB"
database_name = "my-database-prod"
database_id = "prod-database-id"

部署到环境

# 部署到暂存环境
npx wrangler deploy --env staging

# 部署到生产环境
npx wrangler deploy --env production

# 应用暂存环境的迁移
npx wrangler d1 migrations apply my-database-staging --remote --env staging

测试

集成测试

// tests/api.test.ts
import { unstable_dev } from 'wrangler';
import type { UnstableDevWorker } from 'wrangler';
import { describe, beforeAll, afterAll, it, expect } from 'vitest';

describe('API', () => {
  let worker: UnstableDevWorker;

  beforeAll(async () => {
    worker = await unstable_dev('src/index.ts', {
      experimental: { disableExperimentalWarning: true }
    });
  });

  afterAll(async () => {
    await worker.stop();
  });

  it('should list users', async () => {
    const res = await worker.fetch('/users');
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(Array.isArray(data)).toBe(true);
  });

  it('should create user', async () => {
    const res = await worker.fetch('/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email: 'test@test.com', name: 'Test' })
    });
    expect(res.status).toBe(201);
  });
});

CLI快速参考

# 数据库
wrangler d1 create <name>                    # 创建数据库
wrangler d1 list                             # 列出数据库
wrangler d1 info <name>                      # 数据库信息
wrangler d1 delete <name>                    # 删除数据库

# 迁移
wrangler d1 migrations list <name>           # 列出迁移
wrangler d1 migrations apply <name> --local  # 本地应用
wrangler d1 migrations apply <name> --remote # 生产应用

# SQL执行
wrangler d1 execute <name> --command "SQL"   # 运行SQL
wrangler d1 execute <name> --file ./file.sql # 运行SQL文件
wrangler d1 execute <name> --local           # 本地运行
wrangler d1 execute <name> --remote          # 生产运行

# 开发
wrangler dev                                 # 启动本地服务器
wrangler types                               # 生成TypeScript类型
wrangler deploy                              # 生产部署

# Drizzle
drizzle-kit generate                         # 生成迁移
drizzle-kit migrate                          # 应用迁移
drizzle-kit studio                           # 打开Drizzle Studio
drizzle-kit push                             # 推送模式(仅开发)

D1限制和考虑因素

限制
数据库大小 最大10 GB
行大小 最大1 MB
SQL语句 最大100 KB
批量大小 1000条语句
每日读取(免费) 500万
每日写入(免费) 10万

反模式

  • 单一大型数据库 - 设计多个较小的数据库(每个租户)
  • 无迁移 - 总是版本控制模式更改
  • 到处使用原始SQL - 使用Drizzle进行类型安全
  • 无连接到远程 - 总是在部署前测试真实的D1
  • D1中的大BLOB - 使用R2进行文件存储
  • 复杂连接 - D1是SQLite;保持查询简单
  • 无批量 - 使用批量进行多个操作
  • 忽略限制 - 监控免费层的使用情况