mcpField
C
cloudflare-workers

cloudflare-workers

github.com/bagelholeクラウド・DevOps1.2k315 回インストール23 週間前に更新
リポジトリ

cloudflare-workers について

Cloudflare のエッジで JavaScript と TypeScript の関数を開発・デプロイするためのスキルです。API、ミドルウェア、定期実行、ストレージバインディング、Wrangler の設定を扱います。

主な機能

  1. 01エッジ関数のデプロイ
  2. 02API とマイクロサービスの構築
  3. 03ミドルウェアとルートの設定
  4. 04KV、D1、R2 バインディングの利用
  5. 05Cron トリガーの設定

活用シーン

  1. 01軽量なエッジ API の構築
  2. 02オリジン前へのレート制限追加
  3. 03定期的なバックグラウンド処理
  4. 04Webhook の処理や画像変換

インストール

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

$npx skills add https://github.com/bagelhole/devops-security-agent-skills --skill cloudflare-workers

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

詳細

Cloudflare Workers

サブミリ秒のコールドスタートで、JavaScript と TypeScript の関数を Cloudflare のグローバルエッジネットワークにデプロイします。

使用する場面

  • エッジで軽量な API やマイクロサービスを構築する場合。
  • オリジンサーバーの前段にミドルウェア(認証、レート制限、ヘッダーの挿入)を追加する場合。
  • インフラを維持せずに、スケジュールに沿った cron ジョブを実行する場合。
  • Webhook、画像変換、A/B テストのロジックを処理する場合。
  • KV、D1、R2 のストレージバインディングから動的コンテンツを配信する場合。

前提条件

  • ローカルに Node.js 18+ がインストールされていること。
  • Wrangler CLI:npm install -g wrangler。
  • Cloudflare アカウント(無料プランでは 1 日あたり 100,000 リクエストを利用できます)。
  • 認証済みであること:wrangler login を実行するか、CLOUDFLARE_API_TOKEN を設定してください。

クイックスタート

# Scaffold a new Worker project
npm create cloudflare@latest my-worker
cd my-worker

# Login to Cloudflare
npx wrangler login

# Start local development server (port 8787)
npx wrangler dev

# Deploy to production
npx wrangler deploy

Wrangler の基本コマンド

# Local development with remote bindings (KV, D1, R2)
npx wrangler dev --remote

# Deploy to a specific environment
npx wrangler deploy --env staging

# Set a secret (prompts for value)
npx wrangler secret put API_TOKEN
npx wrangler secret put API_TOKEN --env staging

# List secrets
npx wrangler secret list

# Tail production logs in real time
npx wrangler tail

# Tail with filters
npx wrangler tail --status=error --search="timeout"

# View deployment versions
npx wrangler deployments list

# Rollback to a previous deployment
npx wrangler rollback

Wrangler の設定

# wrangler.toml
name = "my-api"
main = "src/index.ts"
compatibility_date = "2024-09-01"
compatibility_flags = ["nodejs_compat"]

# Custom routes
routes = [
  { pattern = "api.example.com/*", zone_name = "example.com" }
]

# Or use a workers.dev subdomain
# workers_dev = true

# Environment variables (non-secret)
[vars]
ENVIRONMENT = "production"
API_VERSION = "v2"

# Staging environment override
[env.staging]
name = "my-api-staging"
routes = [
  { pattern = "api-staging.example.com/*", zone_name = "example.com" }
]
[env.staging.vars]
ENVIRONMENT = "staging"

Worker の例

基本的な API ルーター
// src/index.ts
export interface Env {
  ENVIRONMENT: string;
}

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

    switch (url.pathname) {
      case "/":
        return new Response("OK", { status: 200 });

      case "/api/health":
        return Response.json({
          status: "healthy",
          env: env.ENVIRONMENT,
          timestamp: new Date().toISOString(),
        });

      case "/api/data":
        if (request.method !== "POST") {
          return new Response("Method Not Allowed", { status: 405 });
        }
        const body = await request.json();
        // Process in the background after returning response
        ctx.waitUntil(logToAnalytics(body));
        return Response.json({ received: true });

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

async function logToAnalytics(data: unknown): Promise<void> {
  await fetch("https://analytics.example.com/ingest", {
    method: "POST",
    body: JSON.stringify(data),
    headers: { "Content-Type": "application/json" },
  });
}
ミドルウェア:KV によるレート制限
// src/rate-limiter.ts
interface Env {
  RATE_LIMIT_KV: KVNamespace;
  ORIGIN_URL: string;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const ip = request.headers.get("CF-Connecting-IP") || "unknown";
    const key = `ratelimit:${ip}`;
    const window = 60; // seconds
    const maxRequests = 100;

    const current = parseInt((await env.RATE_LIMIT_KV.get(key)) || "0");

    if (current >= maxRequests) {
      return new Response("Too Many Requests", {
        status: 429,
        headers: { "Retry-After": String(window) },
      });
    }

    await env.RATE_LIMIT_KV.put(key, String(current + 1), {
      expirationTtl: window,
    });

    // Forward to origin
    const originRequest = new Request(env.ORIGIN_URL + new URL(request.url).pathname, request);
    return fetch(originRequest);
  },
};

KV ストレージバインディング

# wrangler.toml
[[kv_namespaces]]
binding = "MY_KV"
id = "abc123def456"

# Preview namespace for local dev
[[kv_namespaces]]
binding = "MY_KV"
id = "abc123def456"
preview_id = "preview789"
// KV operations in a Worker
interface Env {
  MY_KV: KVNamespace;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Write with TTL
    await env.MY_KV.put("session:abc", JSON.stringify({ user: "alice" }), {
      expirationTtl: 3600,
    });

    // Read
    const session = await env.MY_KV.get("session:abc", "json");

    // List keys by prefix
    const list = await env.MY_KV.list({ prefix: "session:", limit: 100 });

    // Delete
    await env.MY_KV.delete("session:abc");

    return Response.json({ session, keys: list.keys.length });
  },
};
# KV CLI operations
npx wrangler kv namespace create MY_KV
npx wrangler kv namespace list
npx wrangler kv key put --namespace-id=abc123 "config:feature-flags" '{"darkMode":true}'
npx wrangler kv key get --namespace-id=abc123 "config:feature-flags"
npx wrangler kv key list --namespace-id=abc123 --prefix="config:"

D1 データベースバインディング

# wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "my-app"
database_id = "xxxx-yyyy-zzzz"
// D1 SQL queries in a Worker
interface Env {
  DB: D1Database;
}

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

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

    // Batch multiple statements
    await env.DB.batch([
      env.DB.prepare("UPDATE users SET active = 0 WHERE last_login < ?").bind("2024-01-01"),
      env.DB.prepare("DELETE FROM sessions WHERE expires_at < ?").bind(Date.now()),
    ]);

    return Response.json(results);
  },
};
# D1 CLI operations
npx wrangler d1 create my-app
npx wrangler d1 list
npx wrangler d1 execute my-app --command="CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT, active INTEGER DEFAULT 1)"
npx wrangler d1 execute my-app --file=./migrations/001_init.sql
npx wrangler d1 execute my-app --command="SELECT * FROM users" --json

Cron トリガー

# wrangler.toml
[triggers]
crons = [
  "0 */6 * * *",   # Every 6 hours
  "0 0 * * MON",   # Every Monday at midnight
  "*/15 * * * *",  # Every 15 minutes
]
// src/index.ts — scheduled handler
export default {
  async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
    switch (event.cron) {
      case "0 */6 * * *":
        ctx.waitUntil(cleanupExpiredSessions(env));
        break;
      case "0 0 * * MON":
        ctx.waitUntil(generateWeeklyReport(env));
        break;
    }
  },

  async fetch(request: Request, env: Env): Promise<Response> {
    return new Response("OK");
  },
};

Durable Objects

# wrangler.toml
[durable_objects]
bindings = [
  { name = "COUNTER", class_name = "Counter" }
]

[[migrations]]
tag = "v1"
new_classes = ["Counter"]
// src/counter.ts — Durable Object class
export class Counter {
  state: DurableObjectState;

  constructor(state: DurableObjectState) {
    this.state = state;
  }

  async fetch(request: Request): Promise<Response> {
    let count = (await this.state.storage.get<number>("count")) || 0;
    count++;
    await this.state.storage.put("count", count);
    return Response.json({ count });
  }
}

// src/index.ts — route to Durable Object
interface Env {
  COUNTER: DurableObjectNamespace;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const id = env.COUNTER.idFromName("global-counter");
    const stub = env.COUNTER.get(id);
    return stub.fetch(request);
  },
};

カスタムルーティング

# Route to specific zones
routes = [
  { pattern = "api.example.com/v1/*", zone_name = "example.com" },
  { pattern = "api.example.com/v2/*", zone_name = "example.com" },
]

# Or use custom domains (automatic SSL)
# Dashboard: Workers > your-worker > Triggers > Custom Domains

トラブルシューティング

症状原因対処方法
Error 1101: Worker threw exceptionfetch ハンドラーで未処理のエラーが発生しているハンドラーを try/catch で囲み、wrangler tail でスタックトレースを確認します
exceeded CPU time limitWorker が CPU 時間の上限である 10ms(無料プラン)または 30s(有料プラン)を超えているコードを最適化し、ctx.waitUntil() で処理をオフロードします
KV の読み取りで古いデータが返されるKV は結果整合性です(約 60 秒)cacheTtl オプションを使用するか、強い一貫性が必要な場合は Durable Objects に切り替えます
wrangler dev のバインディングエラーローカルバインディングが設定されていない--remote フラグを使用するか、wrangler.toml で preview_id を設定します
Worker でシークレットが見つからないシークレットが誤った環境に設定されているwrangler secret list --env <env> で確認します
ブラウザーで CORS エラーが発生するレスポンスに CORS ヘッダーがないAccess-Control-Allow-Origin ヘッダーを追加し、OPTIONS プリフライトを処理します
ルートが一致しないパターンに /* のサフィックスが含まれていないすべてのパスを対象にするには /* を追加します:api.example.com/*

関連スキル

コメント