| name | workers-development |
| description | Use when user asks about Workers development, edge functions, bindings, service bindings, cron triggers, or Workers runtime. Also use when user says Workers 開発, エッジ関数, バインディング, サービス連携. Cloudflare Workers の開発ガイドで、アーキテクチャ、TypeScript/JavaScript 開発、バインディング、Service Bindings、Cron Triggers、Workers Assets を提供する。 |
| context | fork |
Cloudflare Workers Development
概要
Cloudflare Workers は、世界330以上のデータセンターで動作するサーバーレスエッジコンピューティングプラットフォーム。
V8 Isolates ベースでコールドスタートがほぼゼロ、低レイテンシを実現。
アーキテクチャ
V8 Isolates
- Chrome の V8 エンジンをベースにした軽量な実行環境
- 各リクエストが独立した Isolate で実行
- コンテナや VM より高速な起動
制限事項
| 項目 | Free プラン | Paid プラン |
|---|
| CPU 時間 | 10ms | 50ms(バースト可) |
| メモリ | 128MB | 128MB |
| サブリクエスト | 50/リクエスト | 1000/リクエスト |
| スクリプトサイズ | 1MB | 10MB |
基本構造
ES Modules 形式(推奨)
export interface Env {
MY_KV: KVNamespace;
DB: D1Database;
MY_BUCKET: R2Bucket;
AI: Ai;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/api/data") {
const data = await env.MY_KV.get("key");
return Response.json({ data });
}
return new Response("Hello Workers!");
},
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
ctx.waitUntil(doBackgroundWork(env));
},
async queue(batch: MessageBatch, env: Env, ctx: ExecutionContext): Promise<void> {
for (const message of batch.messages) {
console.log(message.body);
message.ack();
}
},
};
Service Worker 形式(レガシー)
addEventListener("fetch", (event) => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
return new Response("Hello!");
}
バインディング
Workers から他の Cloudflare サービスに接続するための設定。
wrangler.toml での設定
[[kv_namespaces]]
binding = "MY_KV"
id = "xxx"
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "my-bucket"
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "xxx"
[durable_objects]
bindings = [
{ name = "COUNTER", class_name = "Counter" }
]
[ai]
binding = "AI"
[[vectorize]]
binding = "VECTORIZE"
index_name = "my-index"
[[services]]
binding = "AUTH"
service = "auth-worker"
[[queues.producers]]
binding = "MY_QUEUE"
queue = "my-queue"
[[analytics_engine_datasets]]
binding = "ANALYTICS"
dataset = "my-dataset"
TypeScript 型定義
export interface Env {
MY_KV: KVNamespace;
MY_BUCKET: R2Bucket;
DB: D1Database;
COUNTER: DurableObjectNamespace;
AI: Ai;
VECTORIZE: VectorizeIndex;
AUTH: Fetcher;
MY_QUEUE: Queue;
API_URL: string;
API_KEY: string;
}
Service Bindings
Workers 間の直接通信。HTTP オーバーヘッドなし。
設定
[[services]]
binding = "AUTH_SERVICE"
service = "auth-worker"
environment = "production"
使用例
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const authResponse = await env.AUTH_SERVICE.fetch(
new Request("https://auth/verify", {
method: "POST",
body: JSON.stringify({ token: "xxx" }),
})
);
if (!authResponse.ok) {
return new Response("Unauthorized", { status: 401 });
}
return new Response("Authorized!");
},
};
Cron Triggers
定期実行ジョブ。
設定
[triggers]
crons = [
"0 * * * *",
"30 8 * * 1-5",
"0 0 1 * *",
]
ハンドラー
export default {
async scheduled(
event: ScheduledEvent,
env: Env,
ctx: ExecutionContext
): Promise<void> {
console.log(`Cron: ${event.cron}`);
console.log(`Scheduled: ${new Date(event.scheduledTime)}`);
ctx.waitUntil(
env.DB.prepare("DELETE FROM logs WHERE created_at < ?")
.bind(Date.now() - 7 * 24 * 60 * 60 * 1000)
.run()
);
},
};
Workers Assets
静的ファイルの配信(Workers Sites の後継)。
設定
[assets]
directory = "./public"
binding = "ASSETS"
動作
- リクエスト URL が静的ファイルに一致 → 自動配信
- 一致しない → Worker スクリプト実行
プログラムからのアクセス
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const assetResponse = await env.ASSETS.fetch(request);
if (assetResponse.status !== 404) {
return assetResponse;
}
return new Response("Not Found", { status: 404 });
},
};
ExecutionContext
waitUntil
レスポンス後もバックグラウンドで処理を継続。
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const response = new Response("OK");
ctx.waitUntil(
env.MY_KV.put("last_access", new Date().toISOString())
);
return response;
},
};
passThroughOnException
エラー時にオリジンにフォールバック。
ctx.passThroughOnException();
Node.js 互換性
有効化
compatibility_flags = ["nodejs_compat"]
使用可能な API
import { Buffer } from "node:buffer";
import { createHash } from "node:crypto";
import { EventEmitter } from "node:events";
const hash = createHash("sha256").update("data").digest("hex");
互換性のあるモジュール
node:buffer
node:crypto
node:events
node:stream
node:util
node:assert
node:path
fetch API
外部 API 呼び出し
const response = await fetch("https://api.example.com/data", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${env.API_KEY}`,
},
body: JSON.stringify({ key: "value" }),
});
const data = await response.json();
リクエスト変換
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
url.hostname = "api.example.com";
const modifiedRequest = new Request(url, {
method: request.method,
headers: request.headers,
body: request.body,
});
return fetch(modifiedRequest);
},
};
ミドルウェアパターン
type Middleware = (
request: Request,
env: Env,
ctx: ExecutionContext,
next: () => Promise<Response>
) => Promise<Response>;
const withAuth: Middleware = async (request, env, ctx, next) => {
const token = request.headers.get("Authorization");
if (!token) {
return new Response("Unauthorized", { status: 401 });
}
return next();
};
const withCors: Middleware = async (request, env, ctx, next) => {
const response = await next();
response.headers.set("Access-Control-Allow-Origin", "*");
return response;
};
const middlewares = [withCors, withAuth];
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
let handler = async () => new Response("Hello!");
for (const mw of middlewares.reverse()) {
const next = handler;
handler = () => mw(request, env, ctx, next);
}
return handler();
},
};
テスト
Vitest(推奨)
import { env, createExecutionContext, waitOnExecutionContext } from "cloudflare:test";
import { describe, it, expect } from "vitest";
import worker from "./index";
describe("Worker", () => {
it("returns hello", async () => {
const request = new Request("https://example.com/");
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
await waitOnExecutionContext(ctx);
expect(response.status).toBe(200);
expect(await response.text()).toBe("Hello!");
});
});
vitest.config.ts
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: "./wrangler.toml" },
},
},
},
});
デバッグ
console.log
console.log("Debug:", { key: value });
console.error("Error:", error);
console.time("operation");
console.timeEnd("operation");
wrangler tail
npx wrangler tail --format pretty
公式リソース