Cloudflare Workers + D1 最小サンプル

Pages Functions と D1 データベースを組み合わせた最小構成の動作サンプルです。

1ライブデモ

下のボタンを押すと /api/d1 を実際に呼び出します。この Pages Function は D1 に接続し、テーブル作成 → 1 行 INSERT → 集計 SELECT を行った結果をそのまま JSON で返します。 つまりこの結果が表示されれば、Workers Functions + D1 が本番環境で実際に動いている証拠になります。

2このアプリの全ソース

このサイトを構成するファイルはこれだけです。そのままコピーして使えます。

wrangler.toml
name = "wrangler-d1-sample"
pages_build_output_dir = "public"
compatibility_date = "2026-07-01"

[[d1_databases]]
binding = "DB"
database_name = "showcase_db"
database_id = "10837764-4119-4b99-8e9c-6dc8d85e42f6"
functions/api/d1.js
export async function onRequestGet(context) {
  const { env } = context;
  const db = env.DB;

  const corsHeaders = {
    "content-type": "application/json",
    "access-control-allow-origin": "*",
  };

  try {
    await db
      .prepare(
        "CREATE TABLE IF NOT EXISTS sample_visits (id INTEGER PRIMARY KEY AUTOINCREMENT, ts TEXT)"
      )
      .run();

    await db
      .prepare("INSERT INTO sample_visits (ts) VALUES (datetime('now'))")
      .run();

    const nowResult = await db.prepare("SELECT datetime('now') AS now").first();

    const countResult = await db
      .prepare("SELECT COUNT(*) AS total FROM sample_visits")
      .first();

    const recentResult = await db
      .prepare("SELECT id, ts FROM sample_visits ORDER BY id DESC LIMIT 5")
      .all();

    const body = {
      ok: true,
      now: nowResult ? nowResult.now : null,
      total_visits: countResult ? countResult.total : 0,
      recent: recentResult.results,
    };

    return new Response(JSON.stringify(body), {
      status: 200,
      headers: corsHeaders,
    });
  } catch (err) {
    return new Response(
      JSON.stringify({ ok: false, error: String(err && err.message ? err.message : err) }),
      { status: 500, headers: corsHeaders }
    );
  }
}
デプロイに使ったコマンド
# D1 は既存の showcase_db を再利用(新規作成しない)
npx wrangler pages project create wrangler-d1-sample --production-branch=main

# public/ をビルド出力として Functions ごとデプロイ
npx wrangler pages deploy public --project-name=wrangler-d1-sample --branch=main --commit-dirty=true

3ローカル開発の手順

パターンA: ローカルモード(ローカル D1、ネットワーク不要)
npx wrangler dev

D1 もローカルの SQLite(.wrangler/state 配下)に対して動作します。 本番データには一切触れないため、まず試すならこちら。

パターンB: リモートバインディング(本番の D1 に直接接続)
[[d1_databases]]
binding = "DB"
database_name = "showcase_db"
database_id = "10837764-4119-4b99-8e9c-6dc8d85e42f6"
experimental_remote = true
npx wrangler dev --x-remote-bindings

バインディングに experimental_remote = true を追加し、 --x-remote-bindings フラグを付けて起動すると、ローカルの Function コードから 本番の D1 データベースへ直接読み書きできます。本番データを扱う分、取り扱いは慎重に。

4write EOF / Failed to start ProxyWorker(Windows)の対処

前提知識: wrangler dev--remote の有無にかかわらず、 ローカルで workerd.exe(ネイティブバイナリ)を起動して動きます。 Failed to start ProxyWorker ... write EOF というエラーは、 「起動直後に workerd プロセスが死に、wrangler がその stdin へ書き込んだ結果 EOF になった」ことを意味します。 これは Windows 環境で wrangler 3.15〜4.23 の間、長期にわたって報告され続けている既知のバグ群です (workers-sdk#4412, #6262, #7013, #8224, #9900, workerd#4668)。

  1. 最有力の修正: Microsoft Visual C++ 2015–2022 再頒布可能パッケージを x64 と x86 の両方 インストールし、ターミナルを再起動してください。
    winget install Microsoft.VCRedist.2015+.x64
    winget install Microsoft.VCRedist.2015+.x86
    この症状にズバリ効いたという修正報告があります(下記リンクの dev.to 記事参照)。
  2. 切り分けコマンド: workerd.exe を直接叩いて、 そもそも起動できるかを確認します。
    node_modules\@cloudflare\workerd-windows-64\bin\workerd.exe --version
    DLL エラーが出る、または無言で終了する場合は workerd 自体が起動できていない =ランタイム(VC++ 再頒布可能パッケージ)の問題で確定です。バージョン文字列が表示される場合は別の要因を疑ってください。
  3. 追加で試す: IPv6/localhost 名前解決が原因のケースの切り分けとして、
    set NODE_OPTIONS=--dns-result-order=ipv4first
    を設定してから起動してみてください(workers-sdk#12910)。 また、--remote フラグの代わりに、wrangler.toml の D1 バインディングに experimental_remote = true を追加し npx wrangler dev --x-remote-bindings を使う方法も、症状が出るパス自体を回避できる場合があります(本ページの「3. ローカル開発の手順」参照)。
  4. それでも解決しない場合: WSL2 上で開発するのが最も確実な回避策です。 workerd の Windows ネイティブ版には未修正の問題が複数残っていますが、 WSL2(Linux バイナリ)を使えばこれらは発生しません。VS Code の WSL 拡張を使えば、 開発体験はネイティブ Windows とほぼ変わりません。

参考リンク: workers-sdk#8224workerd#4668dev.to/mrtoxas: Wrangler write EOF on Windows — the actual fix