mcpField
C
cloudflare-d1

cloudflare-d1

github.com/ovachieverデータ・データベース5649 回インストール48 週間前に更新
リポジトリ

cloudflare-d1 について

cloudflare-d1 は、Workers から Cloudflare D1 を利用する開発者向けの実践ガイドです。データベース設定、マイグレーション、クエリ、ローカル開発を Wrangler の例とともに扱います。

主な機能

  1. 01データベースとバインディングの設定
  2. 02バージョン管理された SQL マイグレーション
  3. 03プリペアドステートメントとバッチクエリ
  4. 04ローカルおよびリモートのデータベース運用
  5. 05クエリ性能とエラー対応のガイド

活用シーン

  1. 01D1 データベースの作成とマイグレーション
  2. 02Worker からの D1 クエリ
  3. 03関連するデータベース操作の一括処理
  4. 04スキーマ変更のローカルテスト

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/ovachiever/droid-tings --skill cloudflare-d1

インストール後、Claude Code で/cloudflare-d1を入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

README.md
.claude-plugin/
plugin.json
references/
best-practices.md
query-patterns.md
templates/
d1-setup-migration.sh
d1-worker-queries.ts
schema-example.sql

詳細

Cloudflare D1 Database

ステータス:本番環境で利用可能 ✅ 最終更新日:2025-11-23 依存関係:cloudflare-worker-base(Worker のセットアップ用) 最新バージョン:[email protected]、@cloudflare/[email protected]

最近の更新(2025年):

  • 2025年11月:管轄区域のサポート(データローカライゼーションへの準拠)、リモートバインディングが GA に([email protected] 以降)、リソースの自動プロビジョニング
  • 2025年9月:読み取り専用クエリの自動再試行(最大 2 回)、リモートバインディングがパブリックベータに
  • 2025年7月:ストレージ上限を引き上げ(250GB → 1TB)、アルファ版のバックアップアクセスを削除、REST API が 50~500ms 高速化
  • 2025年5月:HTTP API の権限に関するセキュリティ修正(書き込みには D1:Edit が必要)
  • 2025年4月:読み取りレプリケーションがパブリックベータに(複数リージョンにまたがる読み取り専用レプリカ)
  • 2025年2月:PRAGMA optimize のサポート、読み取り専用アクセス権限のバグ修正
  • 2025年1月:無料プランの上限適用(2月10日開始)、Worker API のクエリが 40~60% 高速化

クイックスタート(5 分)

1. D1 データベースを作成する
# Create a new D1 database
npx wrangler d1 create my-database

# Output includes database_id - save this!
# ✅ Successfully created DB 'my-database'
#
# [[d1_databases]]
# binding = "DB"
# database_name = "my-database"
# database_id = "<UUID>"
2. バインディングを設定する

wrangler.jsonc に追加します。

{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2025-10-11",
  "d1_databases": [
    {
      "binding": "DB",                    // Available as env.DB in your Worker
      "database_name": "my-database",      // Name from wrangler d1 create
      "database_id": "<UUID>",             // ID from wrangler d1 create
      "preview_database_id": "local-db"    // For local development
    }
  ]
}

重要:

  • binding はコード内でデータベースにアクセスするための名前です(env.DB)
  • database_id は本番データベースの UUID です
  • preview_database_id はローカル開発用です(任意の文字列を指定できます)
  • 実際の database_id の値を公開リポジトリにコミットしないでください。環境変数またはシークレットを使用してください
3. 最初のマイグレーションを作成する
# Create migration file
npx wrangler d1 migrations create my-database create_users_table

# This creates: migrations/0001_create_users_table.sql

マイグレーションファイルを編集します。

-- migrations/0001_create_users_table.sql
DROP TABLE IF EXISTS users;
CREATE TABLE IF NOT EXISTS users (
  user_id INTEGER PRIMARY KEY AUTOINCREMENT,
  email TEXT NOT NULL UNIQUE,
  username TEXT NOT NULL,
  created_at INTEGER NOT NULL,
  updated_at INTEGER
);

-- Create index for common queries
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);

-- Optimize database
PRAGMA optimize;
4. マイグレーションを適用する
# Apply locally first (for testing)
npx wrangler d1 migrations apply my-database --local

# Apply to production when ready
npx wrangler d1 migrations apply my-database --remote
5. Worker からクエリを実行する
// src/index.ts
import { Hono } from 'hono';

type Bindings = {
  DB: D1Database;
};

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

app.get('/api/users/:email', async (c) => {
  const email = c.req.param('email');

  try {
    // ALWAYS use prepared statements with bind()
    const result = await c.env.DB.prepare(
      'SELECT * FROM users WHERE email = ?'
    )
    .bind(email)
    .first();

    if (!result) {
      return c.json({ error: 'User not found' }, 404);
    }

    return c.json(result);
  } catch (error: any) {
    console.error('D1 Error:', error.message);
    return c.json({ error: 'Database error' }, 500);
  }
});

export default app;

D1 マイグレーションシステム

マイグレーションのワークフロー
# 1. Create migration
npx wrangler d1 migrations create <DATABASE_NAME> <MIGRATION_NAME>

# 2. List unapplied migrations
npx wrangler d1 migrations list <DATABASE_NAME> --local
npx wrangler d1 migrations list <DATABASE_NAME> --remote

# 3. Apply migrations
npx wrangler d1 migrations apply <DATABASE_NAME> --local   # Test locally
npx wrangler d1 migrations apply <DATABASE_NAME> --remote  # Deploy to production
マイグレーションファイルの命名

マイグレーションは自動的にバージョン管理されます。

migrations/
├── 0000_initial_schema.sql
├── 0001_add_users_table.sql
├── 0002_add_posts_table.sql
└── 0003_add_indexes.sql

ルール:

  • ファイルは順番に実行されます
  • 各マイグレーションは 1 回だけ実行されます(d1_migrations テーブルで追跡)
  • 失敗したマイグレーションはロールバックされます(トランザクション処理)
  • 適用済みのマイグレーションは変更または削除できません
カスタムマイグレーション設定
{
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "my-database",
      "database_id": "<UUID>",
      "migrations_dir": "db/migrations",        // Custom directory (default: migrations/)
      "migrations_table": "schema_migrations"   // Custom tracking table (default: d1_migrations)
    }
  ]
}
マイグレーションのベストプラクティス
✅ 必ず行うこと:
-- Use IF NOT EXISTS to make migrations idempotent
CREATE TABLE IF NOT EXISTS users (...);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);

-- Run PRAGMA optimize after schema changes
PRAGMA optimize;

-- Use transactions for data migrations
BEGIN TRANSACTION;
UPDATE users SET updated_at = unixepoch() WHERE updated_at IS NULL;
COMMIT;
❌ 絶対に行わないこと:
-- DON'T include BEGIN TRANSACTION at start (D1 handles this)
BEGIN TRANSACTION;  -- ❌ Remove this

-- DON'T use MySQL/PostgreSQL syntax
ALTER TABLE users MODIFY COLUMN email VARCHAR(255);  -- ❌ Not SQLite

-- DON'T create tables without IF NOT EXISTS
CREATE TABLE users (...);  -- ❌ Fails if table exists
マイグレーションでの外部キーの処理
-- Temporarily disable foreign key checks during schema changes
PRAGMA defer_foreign_keys = true;

-- Make schema changes that would violate foreign keys
ALTER TABLE posts DROP COLUMN author_id;
ALTER TABLE posts ADD COLUMN user_id INTEGER REFERENCES users(user_id);

-- Foreign keys re-enabled automatically at end of migration

D1 Workers API

型定義:

interface Env { DB: D1Database; }
type Bindings = { DB: D1Database; };
const app = new Hono<{ Bindings: Bindings }>();

prepare() - 主なメソッド(ユーザー入力には必ず使用):

const user = await env.DB.prepare('SELECT * FROM users WHERE email = ?')
  .bind(email).first();

理由:SQL インジェクションを防止し、再利用可能で、パフォーマンスに優れ、型安全です

クエリ結果のメソッド:

  • .all() → { results, meta } - すべての行を取得
  • .first() → 行オブジェクトまたは null - 最初の行を取得
  • .first('column') → 値 - 単一の列の値を取得(例:COUNT)
  • .run() → { success, meta } - INSERT/UPDATE/DELETE を実行(結果なし)

batch() - パフォーマンス上、非常に重要:

const results = await env.DB.batch([
  env.DB.prepare('SELECT * FROM users WHERE user_id = ?').bind(1),
  env.DB.prepare('SELECT * FROM posts WHERE user_id = ?').bind(1)
]);
  • 順番に実行し、ネットワークの往復は 1 回だけです
  • 1 つ失敗すると、残りのステートメントは実行されません
  • 使用例:一括挿入、関連データの取得

exec() - 本番環境では使用を避ける:

await env.DB.exec('SELECT * FROM users;'); // Only for migrations/maintenance
  • ❌ ユーザー入力と一緒に使用しないでください(SQL インジェクションのリスク)
  • ✅ 使用できるのは、マイグレーションファイルや単発タスクのみです

クエリのパターン

基本的な CRUD 操作
// CREATE
const { meta } = await env.DB.prepare(
  'INSERT INTO users (email, username, created_at) VALUES (?, ?, ?)'
).bind(email, username, Date.now()).run();
const newUserId = meta.last_row_id;

// READ (single)
const user = await env.DB.prepare('SELECT * FROM users WHERE user_id = ?')
  .bind(userId).first();

// READ (multiple)
const { results } = await env.DB.prepare('SELECT * FROM users LIMIT ?')
  .bind(10).all();

// UPDATE
const { meta } = await env.DB.prepare('UPDATE users SET username = ? WHERE user_id = ?')
  .bind(newUsername, userId).run();
const rowsAffected = meta.rows_written;

// DELETE
await env.DB.prepare('DELETE FROM users WHERE user_id = ?').bind(userId).run();

// COUNT
const count = await env.DB.prepare('SELECT COUNT(*) as total FROM users').first('total');

// EXISTS check
const exists = await env.DB.prepare('SELECT 1 FROM users WHERE email = ? LIMIT 1')
  .bind(email).first();
ページネーションのパターン
const page = parseInt(c.req.query('page') || '1');
const limit = 20;
const offset = (page - 1) * limit;

const [countResult, usersResult] = await c.env.DB.batch([
  c.env.DB.prepare('SELECT COUNT(*) as total FROM users'),
  c.env.DB.prepare('SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?')
    .bind(limit, offset)
]);

return c.json({
  users: usersResult.results,
  pagination: { page, limit, total: countResult.results[0].total }
});
バッチのパターン(疑似トランザクション)
// D1 doesn't support multi-statement transactions, but batch() provides sequential execution
await env.DB.batch([
  env.DB.prepare('UPDATE users SET credits = credits - ? WHERE user_id = ?').bind(amount, fromUserId),
  env.DB.prepare('UPDATE users SET credits = credits + ? WHERE user_id = ?').bind(amount, toUserId),
  env.DB.prepare('INSERT INTO transactions (from_user, to_user, amount) VALUES (?, ?, ?)').bind(fromUserId, toUserId, amount)
]);
// If any statement fails, batch stops (transaction-like behavior)

エラー処理

一般的なエラーの種類:

  • D1_ERROR - 一般的な D1 エラー
  • D1_EXEC_ERROR - SQL 構文エラー
  • D1_TYPE_ERROR - 型の不一致(null の代わりに undefined)
  • D1_COLUMN_NOTFOUND - カラムが存在しません

よくあるエラーと修正方法:

エラー原因解決策
ステートメントが長すぎます1000 行以上の大規模な INSERTbatch() を使って 100~250 行ずつに分割します
キューに登録されたリクエストが多すぎますループ内で個別にクエリを実行ループの代わりに batch() を使います
D1_TYPE_ERRORbind で undefined を使用オプションの値には null を使います:.bind(email, bio || null)
トランザクションの競合マイグレーション内の BEGIN TRANSACTIONBEGIN/COMMIT を削除します(D1 が自動的に処理します)
外部キー制約違反スキーマ変更によって制約が破損PRAGMA defer_foreign_keys = true を使います

自動再試行(2025 年 9 月): D1 は再試行可能なエラーが発生した場合、読み取り専用クエリ(SELECT、EXPLAIN、WITH)を最大 2 回、自動的に再試行します。再試行回数を確認するには、レスポンスの meta.total_attempts を確認してください。


パフォーマンスの最適化

インデックスのベストプラクティス:

  • ✅ WHERE 句で使うカラムにインデックスを作成します:CREATE INDEX idx_users_email ON users(email)
  • ✅ 外部キーにインデックスを作成します:CREATE INDEX idx_posts_user_id ON posts(user_id)
  • ✅ ソートに使うカラムにインデックスを作成します:CREATE INDEX idx_posts_created_at ON posts(created_at DESC)
  • ✅ 複数カラムのインデックスを作成します:CREATE INDEX idx_posts_user_published ON posts(user_id, published)
  • ✅ 部分インデックスを作成します:CREATE INDEX idx_users_active ON users(email) WHERE deleted = 0
  • ✅ 次のコマンドでテストします:EXPLAIN QUERY PLAN SELECT ...

PRAGMA optimize(2025 年 2 月):

CREATE INDEX idx_users_email ON users(email);
PRAGMA optimize;  -- Run after schema changes

クエリの最適化:

  • ✅ 特定のカラムを指定します(SELECT * は使いません)
  • ✅ 大規模な結果セットには必ず LIMIT を指定します
  • ✅ WHERE 条件にはインデックスを使います
  • ❌ WHERE 句で関数を使うのは避けます(インデックスを使用できません):WHERE LOWER(email) → 代わりに小文字で保存します

ローカル開発

ローカルとリモートの比較(2025 年 11 月 - リモートバインディングの GA):

# Local database (automatic creation)
npx wrangler d1 migrations apply my-database --local
npx wrangler d1 execute my-database --local --command "SELECT * FROM users"

# Remote database
npx wrangler d1 execute my-database --remote --command "SELECT * FROM users"

# Remote bindings ([email protected]+) - connect local Worker to deployed D1
# Add to wrangler.jsonc: { "binding": "DB", "remote": true }

ローカルデータベースの場所: .wrangler/state/v3/d1/miniflare-D1DatabaseObject/<database_id>.sqlite

ローカルデータベースへのデータ投入:

npx wrangler d1 execute my-database --local --file=seed.sql

ベストプラクティスのまとめ

✅ 必ず行うこと:
  1. ユーザー入力には .bind() を使ってプリペアドステートメントを使用する
  2. 複数のクエリには .batch() を使う(レイテンシーを低減します)
  3. 頻繁にクエリするカラムにインデックスを作成する
  4. スキーマ変更後に PRAGMA optimize を実行する
  5. 冪等性を確保するため、マイグレーションで IF NOT EXISTS を使う
  6. 本番環境に適用する前にローカルでマイグレーションをテストする
  7. try/catch を使ってエラーを適切に処理する
  8. オプションの値には undefined ではなく null を使う
  9. クエリにバインドする前に入力を検証する
  10. UPDATE/DELETE の後に meta.rows_written を確認する
❌ 絶対に行わないこと:
  1. ユーザー入力に .exec() を絶対に使わない(SQL インジェクションのリスクがあります)
  2. 公開リポジトリに database_id を絶対にハードコードしない
  3. bind パラメーターに undefined を絶対に使わない(D1_TYPE_ERROR の原因になります)
  4. ループ内で個別のクエリを絶対に実行しない(代わりに batch を使います)
  5. 大規模になる可能性がある結果セットで LIMIT を絶対に忘れない
  6. 本番環境で SELECT * を絶対に使わない(カラムを指定します)
  7. マイグレーションファイルに BEGIN TRANSACTION を絶対に含めない
  8. 適用済みのマイグレーションを絶対に変更しない(新しいものを作成します)
  9. データベース操作でのエラー処理を絶対に省略しない
  10. クエリが成功すると絶対に決めつけない(必ず結果を確認します)

防止される既知の問題

問題説明回避方法
ステートメントが長すぎる大きな INSERT ステートメントが D1 の制限を超えます100~250 行ずつに分割します
トランザクションの競合マイグレーションファイル内の BEGIN TRANSACTIONBEGIN/COMMIT を削除します(D1 が処理します)
外部キー制約違反スキーマの変更によって外部キー制約が破られますPRAGMA defer_foreign_keys = true を使用します
レート制限/キューの過負荷個別のクエリが多すぎますループではなく batch() を使用します
メモリ制限超過クエリがメモリに大量のデータを読み込みますLIMIT を追加し、結果をページ分割し、クエリをシャーディングします
型の不一致エラーnull の代わりに undefined を使用していますオプションの値には必ず null を使用します

Wrangler コマンドのリファレンス

# Database management
wrangler d1 create <DATABASE_NAME>
wrangler d1 list
wrangler d1 delete <DATABASE_NAME>
wrangler d1 info <DATABASE_NAME>

# Migrations
wrangler d1 migrations create <DATABASE_NAME> <MIGRATION_NAME>
wrangler d1 migrations list <DATABASE_NAME> --local|--remote
wrangler d1 migrations apply <DATABASE_NAME> --local|--remote

# Execute queries
wrangler d1 execute <DATABASE_NAME> --local|--remote --command "SELECT * FROM users"
wrangler d1 execute <DATABASE_NAME> --local|--remote --file=./query.sql

# Time Travel (view historical data)
wrangler d1 time-travel info <DATABASE_NAME> --timestamp "2025-10-20"
wrangler d1 time-travel restore <DATABASE_NAME> --timestamp "2025-10-20"

公式ドキュメント


D1 での構築を始めましょう! 🚀

コメント