cloudflare-d1 by alinaqi/claude-bootstrap
npx skills add https://github.com/alinaqi/claude-bootstrap --skill cloudflare-d1加载方式:base.md + typescript.md
Cloudflare D1 是一个为 Cloudflare Workers 设计的无服务器 SQLite 数据库,具备全球分布和零冷启动特性。
资料来源: D1 文档 | Drizzle + D1 | Wrangler CLI
边缘的 SQLite,版本控制中的迁移,Drizzle 保证类型安全。
D1 将 SQLite 的简洁性带到了无服务器领域。为水平扩展(多个小型数据库)而非垂直扩展(一个大型数据库)进行设计。使用 Drizzle ORM 进行类型安全的查询和迁移。
| 组件 | 用途 |
|---|---|
| D1 | 无服务器 SQLite 数据库 |
| Workers | 应用程序的边缘运行时 |
| Wrangler | 用于开发和部署的 CLI |
| Drizzle ORM | 类型安全的 ORM,支持迁移 |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 迁移工具 |
| Hono | 轻量级 Web 框架(可选) |
# 创建新项目
npm create cloudflare@latest my-app -- --template "worker-typescript"
cd my-app
# 安装依赖
npm install drizzle-orm
npm install -D drizzle-kit
# 创建数据库(同时创建本地和远程)
npx wrangler d1 create my-database
# 输出:
# [[d1_databases]]
# binding = "DB"
# database_name = "my-database"
# database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
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"
# 从 wrangler.toml 生成环境类型
npx wrangler types
# 创建 worker-configuration.d.ts:
# interface Env {
# DB: D1Database;
# }
// 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.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
# 先预览(试运行)
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
);
// 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: 'User not found' }, 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: 'User not found' }, 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: 'User not found' }, 404);
}
return c.json({ deleted: true });
});
export default app;
// 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);
// Where 子句
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' }
]);
// 更新插入(冲突时插入或更新)
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 通过 batch 支持事务
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 进行可视化数据库管理
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
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);
});
});
# 数据库
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 # 推送模式(仅开发)
| 限制 | 值 |
|---|---|
| 数据库大小 | 最大 10 GB |
| 行大小 | 最大 1 MB |
| SQL 语句 | 最大 100 KB |
| 批量大小 | 1000 条语句 |
| 每日读取次数(免费) | 500 万次 |
| 每日写入次数(免费) | 10 万次 |
每周安装次数
67
代码仓库
GitHub 星标数
529
首次出现
2026年1月20日
安全审计
已安装于
opencode53
claude-code52
gemini-cli48
cursor46
codex46
antigravity43
Load with: base.md + typescript.md
Cloudflare D1 is a serverless SQLite database designed for Cloudflare Workers with global distribution and zero cold starts.
Sources: D1 Docs | Drizzle + D1 | Wrangler CLI
SQLite at the edge, migrations in version control, Drizzle for type safety.
D1 brings SQLite's simplicity to serverless. Design for horizontal scale (multiple small databases) rather than vertical (one large database). Use Drizzle ORM for type-safe queries and migrations.
| Component | Purpose |
|---|---|
| D1 | Serverless SQLite database |
| Workers | Edge runtime for your application |
| Wrangler | CLI for development and deployment |
| Drizzle ORM | Type-safe ORM with migrations |
| Drizzle Kit | Migration tooling |
| Hono | Lightweight web framework (optional) |
# Create new project
npm create cloudflare@latest my-app -- --template "worker-typescript"
cd my-app
# Install dependencies
npm install drizzle-orm
npm install -D drizzle-kit
# Create database (creates both local and remote)
npx wrangler d1 create my-database
# Output:
# [[d1_databases]]
# binding = "DB"
# database_name = "my-database"
# database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
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"
# Generate env types from wrangler.toml
npx wrangler types
# Creates worker-configuration.d.ts:
# interface Env {
# DB: D1Database;
# }
// 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)
});
// Type exports
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.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';
# Generate migration from schema changes
npx drizzle-kit generate
# Output: drizzle/0000_initial.sql
# Apply to local D1
npx wrangler d1 migrations apply my-database --local
# Or via Drizzle
npx drizzle-kit migrate
# Apply to remote D1
npx wrangler d1 migrations apply my-database --remote
# Preview first (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
);
// 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 }>();
// Middleware to inject db
app.use('*', async (c, next) => {
c.set('db', createDb(c.env.DB));
await next();
});
// List users
app.get('/users', async (c) => {
const db = c.get('db');
const allUsers = await db.select().from(users);
return c.json(allUsers);
});
// Get user by 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: 'User not found' }, 404);
}
return c.json(user);
});
// Create 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);
});
// Update user
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: 'User not found' }, 404);
}
return c.json(result[0]);
});
// Delete user
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: 'User not found' }, 404);
}
return c.json({ deleted: true });
});
export default app;
// 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';
// Basic select
const allPosts = await db.select().from(posts);
// Select specific columns
const titles = await db.select({ id: posts.id, title: posts.title }).from(posts);
// Where clause
const published = await db.select().from(posts).where(eq(posts.published, true));
// Multiple conditions
const recentPublished = await db.select().from(posts).where(
and(
eq(posts.published, true),
gt(posts.createdAt, '2024-01-01')
)
);
// OR conditions
const featured = await db.select().from(posts).where(
or(
eq(posts.viewCount, 1000),
like(posts.title, '%featured%')
)
);
// Order and limit
const topPosts = await db.select()
.from(posts)
.orderBy(desc(posts.viewCount))
.limit(10);
// Pagination
const page2 = await db.select()
.from(posts)
.orderBy(desc(posts.createdAt))
.limit(10)
.offset(10);
// Count
const postCount = await db.select({ count: count() }).from(posts);
// Inner join
const postsWithAuthors = await db.select({
post: posts,
author: users
})
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id));
// Left join
const allPostsWithAuthors = await db.select()
.from(posts)
.leftJoin(users, eq(posts.authorId, users.id));
// Many-to-many via junction table
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));
// Insert single
const newUser = await db.insert(users).values({
email: 'user@example.com',
name: 'John Doe'
}).returning();
// Insert multiple
await db.insert(users).values([
{ email: 'a@test.com', name: 'Alice' },
{ email: 'b@test.com', name: 'Bob' }
]);
// Upsert (insert or update on conflict)
await db.insert(users)
.values({ email: 'user@test.com', name: 'New Name' })
.onConflictDoUpdate({
target: users.email,
set: { name: 'New Name' }
});
// Update
await db.update(posts)
.set({ published: true })
.where(eq(posts.id, 1));
// Update with increment
await db.update(posts)
.set({ viewCount: sql`${posts.viewCount} + 1` })
.where(eq(posts.id, 1));
// Delete
await db.delete(posts).where(eq(posts.id, 1));
// D1 supports transactions via batch
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))
]);
// Raw D1 batch
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')
]);
# Local development with D1
npx wrangler dev
# With specific port
npx wrangler dev --port 8787
# Execute SQL locally
npx wrangler d1 execute my-database --local --command "SELECT * FROM users"
# Execute SQL file
npx wrangler d1 execute my-database --local --file ./seed.sql
# Open SQLite shell
npx wrangler d1 execute my-database --local --command ".tables"
# Run Drizzle Studio for visual DB management
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);
# Seed local database
npx wrangler d1 execute my-database --local --file ./seed.sql
name = "my-app"
main = "src/index.ts"
compatibility_date = "2024-01-01"
# Development
[env.dev]
[[env.dev.d1_databases]]
binding = "DB"
database_name = "my-database-dev"
database_id = "dev-database-id"
# Staging
[env.staging]
[[env.staging.d1_databases]]
binding = "DB"
database_name = "my-database-staging"
database_id = "staging-database-id"
# Production
[env.production]
[[env.production.d1_databases]]
binding = "DB"
database_name = "my-database-prod"
database_id = "prod-database-id"
# Deploy to staging
npx wrangler deploy --env staging
# Deploy to production
npx wrangler deploy --env production
# Apply migrations to staging
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);
});
});
# Database
wrangler d1 create <name> # Create database
wrangler d1 list # List databases
wrangler d1 info <name> # Database info
wrangler d1 delete <name> # Delete database
# Migrations
wrangler d1 migrations list <name> # List migrations
wrangler d1 migrations apply <name> --local # Apply locally
wrangler d1 migrations apply <name> --remote # Apply to production
# SQL execution
wrangler d1 execute <name> --command "SQL" # Run SQL
wrangler d1 execute <name> --file ./file.sql # Run SQL file
wrangler d1 execute <name> --local # Run on local
wrangler d1 execute <name> --remote # Run on production
# Development
wrangler dev # Start local server
wrangler types # Generate TypeScript types
wrangler deploy # Deploy to production
# Drizzle
drizzle-kit generate # Generate migrations
drizzle-kit migrate # Apply migrations
drizzle-kit studio # Open Drizzle Studio
drizzle-kit push # Push schema (dev only)
| Limit | Value |
|---|---|
| Database size | 10 GB max |
| Row size | 1 MB max |
| SQL statement | 100 KB max |
| Batch size | 1000 statements |
| Reads per day (free) | 5 million |
| Writes per day (free) | 100,000 |
Weekly Installs
67
Repository
GitHub Stars
529
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode53
claude-code52
gemini-cli48
cursor46
codex46
antigravity43