mcpField
B
bun-cloudflare-workers

bun-cloudflare-workers

github.com/secondskyクラウド・DevOps240406 回インストール5 週間前に更新
リポジトリ

bun-cloudflare-workers について

Bun を使って Cloudflare Workers を開発、デプロイするためのスキルガイドです。ローカル開発、テスト、ビルドに加え、KV、D1、Durable Objects、R2 などのバインディングを扱います。

主な機能

  1. 01Bun を使った Workers プロジェクトの設定
  2. 02Wrangler によるローカル開発
  3. 03bun:test と Miniflare によるテスト
  4. 04KV、D1、Durable Objects、R2 のパターン
  5. 05ビルドとデプロイの解説

活用シーン

  1. 01Workers プロジェクトのひな形を作成
  2. 02Worker をローカルで開発、テスト
  3. 03Cloudflare のストレージやデータベースのバインディングを利用
  4. 04Worker をビルドしてデプロイ

インストール

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

$npx skills add https://github.com/secondsky/claude-skills --skill bun-cloudflare-workers

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

詳細

Bun Cloudflare Workers

Bun を使用して Cloudflare Workers を開発・デプロイします。

クイックスタート

# Create new Workers project
bunx create-cloudflare my-worker
cd my-worker

# Install dependencies
bun install

# Development
bun run dev

# Deploy
bun run deploy

安全なインストール

bunx create-cloudflare のようなスキャフォールディングツールは、リモートコードをダウンロードして実行します。実行する前に、サプライチェーンセキュリティのベストプラクティスに従ってください。

  • インストール後スクリプトをブロックする — Bun はデフォルトで無効にしています。package.json の trustedDependencies で特定のパッケージを許可できます
  • クールダウン期間 — bunfig.toml で minimumReleaseAge を設定し、新しいバージョンの公開後7日間待機します
  • インストール前に監査する — socket package score npm <pkg> を実行するか、socket npm install <pkg> を使用してパッケージを確認します

Socket CLI との統合、クールダウンの設定、ロックファイルの検証、CI での適用など、セキュリティ設定の全容については、dependency-upgrade スキルを読み込んでください。

プロジェクトのセットアップ

package.json
{
  "scripts": {
    "dev": "wrangler dev",
    "deploy": "wrangler deploy",
    "build": "bun build src/index.ts --outdir=dist --target=browser"
  },
  "devDependencies": {
    "@cloudflare/workers-types": "^4.20260408.0",
    "wrangler": "^4.81.0"
  }
}
wrangler.toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"

# Use Bun for local dev
[dev]
local_protocol = "http"

# Bindings
[[kv_namespaces]]
binding = "KV"
id = "xxx"

[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "xxx"

基本的な Worker

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

    if (url.pathname === "/") {
      return new Response("Hello from Cloudflare Workers!");
    }

    if (url.pathname === "/api/data") {
      return Response.json({ message: "Hello" });
    }

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

interface Env {
  KV: KVNamespace;
  DB: D1Database;
}

Hono の使用

// src/index.ts
import { Hono } from "hono";

type Bindings = {
  KV: KVNamespace;
  DB: D1Database;
};

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

app.get("/", (c) => c.text("Hello Hono!"));

app.get("/api/users", async (c) => {
  const users = await c.env.DB.prepare("SELECT * FROM users").all();
  return c.json(users.results);
});

app.post("/api/users", async (c) => {
  const { name } = await c.req.json();
  await c.env.DB.prepare("INSERT INTO users (name) VALUES (?)").bind(name).run();
  return c.json({ success: true });
});

export default app;

KV ストレージ

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const key = url.searchParams.get("key");

    if (request.method === "GET" && key) {
      const value = await env.KV.get(key);
      return Response.json({ key, value });
    }

    if (request.method === "PUT" && key) {
      const value = await request.text();
      await env.KV.put(key, value, { expirationTtl: 3600 });
      return Response.json({ success: true });
    }

    return new Response("Bad Request", { status: 400 });
  },
};

D1 データベース

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Query
    const { results } = await env.DB.prepare(
      "SELECT * FROM users WHERE active = ?"
    ).bind(1).all();

    // Insert
    const info = await env.DB.prepare(
      "INSERT INTO users (name, email) VALUES (?, ?)"
    ).bind("Alice", "[email protected]").run();

    // Transaction
    const batch = await env.DB.batch([
      env.DB.prepare("INSERT INTO users (name) VALUES (?)").bind("Bob"),
      env.DB.prepare("INSERT INTO users (name) VALUES (?)").bind("Charlie"),
    ]);

    return Response.json(results);
  },
};

Durable Objects

// src/counter.ts
export class Counter {
  private state: DurableObjectState;
  private value = 0;

  constructor(state: DurableObjectState) {
    this.state = state;
    this.state.blockConcurrencyWhile(async () => {
      this.value = (await this.state.storage.get("value")) || 0;
    });
  }

  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === "/increment") {
      this.value++;
      await this.state.storage.put("value", this.value);
    }

    return Response.json({ value: this.value });
  }
}

// src/index.ts
export { Counter } from "./counter";

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const id = env.COUNTER.idFromName("global");
    const stub = env.COUNTER.get(id);
    return stub.fetch(request);
  },
};
# wrangler.toml
[[durable_objects.bindings]]
name = "COUNTER"
class_name = "Counter"

[[migrations]]
tag = "v1"
new_classes = ["Counter"]

R2 ストレージ

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const key = url.pathname.slice(1);

    if (request.method === "GET") {
      const object = await env.BUCKET.get(key);
      if (!object) {
        return new Response("Not Found", { status: 404 });
      }
      return new Response(object.body, {
        headers: { "Content-Type": object.httpMetadata?.contentType || "application/octet-stream" },
      });
    }

    if (request.method === "PUT") {
      await env.BUCKET.put(key, request.body, {
        httpMetadata: { contentType: request.headers.get("Content-Type") || undefined },
      });
      return Response.json({ success: true });
    }

    return new Response("Method Not Allowed", { status: 405 });
  },
};

Bun を使用した開発

ローカル開発
# Run with wrangler (uses Bun for TypeScript)
bun run dev

# Or directly
bunx wrangler dev
Bun でのテスト
// src/index.test.ts
import { describe, test, expect } from "bun:test";

// Mock worker
const worker = {
  async fetch(request: Request) {
    return new Response("Hello");
  },
};

describe("Worker", () => {
  test("returns hello", async () => {
    const request = new Request("http://localhost/");
    const response = await worker.fetch(request);
    expect(await response.text()).toBe("Hello");
  });
});
テスト用の Miniflare
import { Miniflare } from "miniflare";

const mf = new Miniflare({
  script: await Bun.file("./dist/index.js").text(),
  kvNamespaces: ["KV"],
});

const response = await mf.dispatchFetch("http://localhost/");
console.log(await response.text());

本番環境向けビルド

// build.ts
await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./dist",
  target: "browser", // Workers use browser APIs
  minify: true,
  sourcemap: "external",
});
bun run build.ts
bunx wrangler deploy

環境変数

# wrangler.toml
[vars]
API_URL = "https://api.example.com"

# Secrets (set via CLI)
# wrangler secret put API_KEY
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    console.log(env.API_URL);    // From vars (non-secret, safe to log)
    // Access a Worker secret (NEVER log it — wrangler tail / Logpush persist logs)
    if (env.API_KEY) {
      // use env.API_KEY to call the upstream API
    }
    // ❌ NEVER: console.log(env.API_KEY) — secrets must not appear in logs
    return new Response("OK");
  },
};

スケジュール実行 Worker(Cron)

export default {
  async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
    console.log("Cron triggered at:", event.scheduledTime);
    // Perform scheduled task
    await env.DB.prepare("DELETE FROM logs WHERE created_at < ?")
      .bind(Date.now() - 7 * 24 * 60 * 60 * 1000)
      .run();
  },

  async fetch(request: Request, env: Env): Promise<Response> {
    return new Response("OK");
  },
};
# wrangler.toml
[triggers]
crons = ["0 * * * *"]  # Every hour

よくあるエラー

エラー原因解決方法
Bun API not availableWorkers は V8 を使用しますWeb API のみを使用してください
Module not foundビルドの問題バンドラーの設定を確認してください
Script too large10MB を超えていますバンドルを最適化してください
CPU time exceeded実行時間が長すぎます最適化するか、キューを使用してください

API の互換性

Workers は Web API をサポートしていますが、Bun 固有の API はサポートしていません。

利用可能利用不可
fetch()Bun.file()
ResponseBun.serve()
Requestbun:sqlite
URLbun:ffi
cryptofs
TextEncoderchild_process

リファレンスを読み込むタイミング

次の場合は references/bindings.md を読み込んでください。

  • 高度な KV/D1/R2 のパターン
  • キューワーカー
  • サービスバインディング

次の場合は references/performance.md を読み込んでください。

  • バンドルの最適化
  • コールドスタートの短縮
  • キャッシュ戦略

コメント