Cloudflare Workers dengan Claude Code: panduan API edge praktis
Bangun API Workers dengan Claude Code, Wrangler, KV/D1/R2, cache, rate limit, logs, dan security headers.
Cloudflare Workers menjalankan JavaScript dan TypeScript di edge network Cloudflare. Edge berarti kode berjalan lebih dekat ke pengguna, bukan hanya di satu region pusat. Ini cocok untuk API kecil, webhook, BFF, response yang di-cache, pemeriksaan keamanan, dan kontrol download dari R2.
Claude Code cocok untuk pekerjaan ini karena struktur Worker jelas: fetch handler, wrangler.toml, dan binding eksplisit seperti KV, D1, dan R2. Dokumentasi resmi dicek ulang pada 3 Juni 2026; buka Workers, Wrangler, bindings, KV, D1, R2, Cache API, Rate Limiting, dan Workers Logs saat implementasi. Untuk perbandingan, lihat serverless functions dan AWS Lambda guide.
Yang akan dibuat
Contohnya adalah API pesanan. D1 menyimpan pesanan, KV membaca setting kecil, R2 menyimpan receipt JSON, Cache API menyimpan response GET yang aman untuk waktu singkat, Rate Limiting menahan abuse, Workers Logs mencatat log terstruktur, dan semua response memiliki security headers.
flowchart LR
Client["Client"] --> Worker["Worker fetch handler"]
Worker --> D1["D1 orders"]
Worker --> KV["KV settings"]
Worker --> R2["R2 receipts"]
Worker --> Cache["Cache API"]
Worker --> Logs["Workers Logs"]
Binding adalah kemampuan eksternal yang disuntikkan ke Worker. Jika wrangler.toml punya binding = "DB", kode memakai resource itu sebagai env.DB.
Prompt untuk Claude Code
Implementasikan API pesanan dengan Cloudflare Workers + TypeScript.
File:
- src/index.ts
- wrangler.toml
- schema.sql
Syarat:
- Pakai format module fetch handler
- GET /health mengembalikan JSON
- GET /orders/:id membaca D1 dan hanya cache output publik yang aman selama 30 detik
- POST /orders validasi JSON lalu insert ke D1
- Validasi Authorization Bearer terhadap API_TOKEN
- Pakai binding SETTINGS KV, DB D1, RECEIPTS R2, API_RATE_LIMITER
- Tambahkan security headers ke semua response
- Log sebagai objek JSON
- Sertakan command curl untuk verifikasi
Jangan:
- Menaruh secret di wrangler.toml
- Mengasumsikan server Node.js yang berjalan terus
- Memberikan pseudocode
Setup Wrangler
npm create cloudflare@latest claude-worker-api -- --type=hello-world
cd claude-worker-api
npm install -D typescript wrangler
npx wrangler --version
C3 bisa membuat wrangler.jsonc pada project baru. Wrangler mendukung JSON/JSONC dan TOML; artikel ini memakai TOML agar mudah dibaca. Jika project Anda sudah memakai wrangler.jsonc, gunakan key yang sama dalam bentuk JSONC.
name = "claude-worker-api"
main = "src/index.ts"
compatibility_date = "2026-06-03"
[vars]
PUBLIC_ENV = "production"
[observability]
enabled = true
head_sampling_rate = 1
[[d1_databases]]
binding = "DB"
database_name = "claude-worker-api"
database_id = "replace-with-d1-database-id"
[[kv_namespaces]]
binding = "SETTINGS"
id = "replace-with-kv-namespace-id"
[[r2_buckets]]
binding = "RECEIPTS"
bucket_name = "claude-worker-receipts"
[[ratelimits]]
name = "API_RATE_LIMITER"
namespace_id = "1001"
[ratelimits.simple]
limit = 60
period = 60
npx wrangler login
npx wrangler d1 create claude-worker-api
npx wrangler kv namespace create SETTINGS
npx wrangler r2 bucket create claude-worker-receipts
npx wrangler secret put API_TOKEN
Skema D1
CREATE TABLE IF NOT EXISTS orders (
id TEXT PRIMARY KEY,
email TEXT NOT NULL,
amount INTEGER NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_orders_email ON orders(email);
npx wrangler d1 execute claude-worker-api --local --file=./schema.sql
npx wrangler d1 execute claude-worker-api --remote --file=./schema.sql
Worker yang bisa dijalankan
export interface Env {
API_TOKEN: string;
PUBLIC_ENV: string;
DB: D1Database;
SETTINGS: KVNamespace;
RECEIPTS: R2Bucket;
API_RATE_LIMITER: RateLimit;
}
const securityHeaders = {
"content-security-policy": "default-src 'none'; frame-ancestors 'none'",
"x-content-type-options": "nosniff",
"x-frame-options": "DENY",
"referrer-policy": "no-referrer",
"permissions-policy": "camera=(), microphone=(), geolocation=()",
};
function json(data: unknown, init: ResponseInit = {}) {
return new Response(JSON.stringify(data), {
...init,
headers: {
"content-type": "application/json; charset=utf-8",
...securityHeaders,
...init.headers,
},
});
}
export default {
async fetch(request, env, ctx): Promise<Response> {
const url = new URL(request.url);
const requestId = crypto.randomUUID();
console.log({ event: "request_started", requestId, method: request.method, path: url.pathname });
if (url.pathname === "/health") {
const maintenance = await env.SETTINGS.get("maintenance");
return json({ ok: true, env: env.PUBLIC_ENV, maintenance: maintenance === "true" });
}
if (request.headers.get("authorization") !== `Bearer ${env.API_TOKEN}`) {
return json({ error: "unauthorized" }, { status: 401 });
}
const { success } = await env.API_RATE_LIMITER.limit({
key: request.headers.get("authorization")?.slice(-16) ?? "anonymous",
});
if (!success) return json({ error: "rate_limited" }, { status: 429 });
const match = url.pathname.match(/^\/orders\/([a-zA-Z0-9_-]+)$/);
if (request.method === "GET" && match) {
const cacheKey = new Request(url.toString(), { method: "GET" });
const cached = await caches.default.match(cacheKey);
if (cached) return cached;
const order = await env.DB.prepare(
"SELECT id, email, amount, status, created_at FROM orders WHERE id = ?"
).bind(match[1]).first();
if (!order) return json({ error: "not_found" }, { status: 404 });
const response = json({ order }, {
headers: { "cache-control": "public, max-age=30", "cache-tag": `order-${match[1]}` },
});
ctx.waitUntil(caches.default.put(cacheKey, response.clone()));
return response;
}
if (request.method === "POST" && url.pathname === "/orders") {
const body = await request.json<{ email?: string; amount?: number }>();
if (!body.email?.includes("@") || !Number.isInteger(body.amount) || body.amount <= 0) {
return json({ error: "invalid_order" }, { status: 400 });
}
const id = crypto.randomUUID();
await env.DB.prepare(
"INSERT INTO orders (id, email, amount, status) VALUES (?, ?, ?, ?)"
).bind(id, body.email, body.amount, "pending").run();
await env.RECEIPTS.put(`orders/${id}.json`, JSON.stringify({ id, email: body.email, amount: body.amount }), {
httpMetadata: { contentType: "application/json" },
});
console.log({ event: "order_created", requestId, orderId: id });
return json({ id, status: "pending" }, { status: 201 });
}
return json({ error: "not_found" }, { status: 404 });
},
} satisfies ExportedHandler<Env>;
Tes lokal dan deploy
printf "API_TOKEN=dev-token\n" > .dev.vars
npx wrangler dev
curl http://localhost:8787/health
curl -X POST http://localhost:8787/orders \
-H "authorization: Bearer dev-token" \
-H "content-type: application/json" \
-d "{\"email\":\"masa@example.com\",\"amount\":1200}"
curl http://localhost:8787/orders/replace-with-created-id \
-H "authorization: Bearer dev-token"
npx wrangler secret put API_TOKEN
npx wrangler d1 execute claude-worker-api --remote --file=./schema.sql
npx wrangler deploy
npx wrangler tail
Use case nyata
Pertama, form atau order API. D1 menyimpan status, R2 menyimpan receipt atau lampiran, dan Workers Logs membantu investigasi.
Kedua, penerima webhook. Verifikasi signature, simpan event ID di D1, lalu abaikan event duplikat.
Ketiga, BFF atau Backend for Frontend. Worker menyembunyikan API key, membentuk ulang response untuk UI, dan cache hanya data yang aman.
Keempat, pengiriman file dari R2. File besar tetap di R2; otorisasi dan log ditangani Worker.
Jebakan umum
Jangan menulis Workers seperti server Node.js yang berjalan terus. Gunakan Web APIs, Request, Response, fetch, crypto, dan bindings.
Jangan campur peran storage. KV untuk key-value kecil, D1 untuk data relasional, R2 untuk object, dan Cache API untuk response pendek.
Jangan cache response privat. Jika bergantung pada cookie, token, email, atau user, hindari shared cache.
Jangan rate limit hanya berdasarkan IP. Gunakan API key, user ID, atau tenant ID jika tersedia.
Memilih platform
Pilih Workers untuk API HTTP low-latency yang dekat dengan cache dan bindings Cloudflare. Pilih Pages Functions jika situs Cloudflare Pages hanya butuh sedikit logic dinamis. Pilih Cloud Run untuk container, job panjang, atau framework penuh. Pilih Lambda jika alurnya berpusat pada AWS S3, DynamoDB, EventBridge, atau SQS.
Prompt review Claude Code
Review implementasi Cloudflare Workers ini.
Cek:
- kompatibilitas Workers runtime
- kesesuaian Env type dan nama binding Wrangler
- secret bocor di code, logs, atau config
- penggunaan bind D1 dan risiko SQL injection
- penggunaan Cache API yang tidak aman
- pilihan key Rate Limiting
- security headers di semua response
- langkah curl yang belum ada
Kembalikan temuan berdasarkan severity, lalu perbaikan dan test.
CTA dan catatan verifikasi
Mulai dari satu endpoint saja: /health, GET read-only, atau webhook. Berikan Claude Code file, nama binding, command verifikasi, dan larangan yang jelas. Untuk template yang bisa dipakai ulang, lihat products; untuk pelatihan tim dan kebiasaan review, lihat training.
Hasil setelah dicoba langsung: Alur artikel ini sudah dirangkai sebagai Wrangler, D1, Worker, dan curl; sebelum produksi, cek ulang wrangler deploy, wrangler tail, headers, cache, dan response 429 di akun Cloudflare Anda.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Workflow Obsidian ke CLAUDE.md untuk Claude Code
Ubah catatan kerja Obsidian menjadi operating note CLAUDE.md agar konteks tidak dijelaskan ulang.
Claude Code Revenue CTA Routing: dari artikel ke PDF, Gumroad, dan konsultasi
Workflow Claude Code untuk mengarahkan pembaca ke PDF gratis, Gumroad, atau konsultasi sesuai intent.
Aturan handoff tim Claude Code: bukti review, permission, rollback, dan jalur revenue
Format handoff Claude Code untuk tim: bukti, permission rule, rollback, PDF gratis, Gumroad, dan konsultasi.