Cloudflare Workers com Claude Code: guia prático de API edge
Crie uma API Workers com Claude Code, Wrangler, KV/D1/R2, cache, rate limiting, logs e headers seguros.
Cloudflare Workers executa JavaScript e TypeScript na rede edge da Cloudflare. Edge quer dizer que o código roda perto do usuário, não apenas em uma região central. Isso combina com APIs pequenas, webhooks, BFFs, respostas em cache, checagens de segurança e controle de downloads no R2.
Claude Code funciona bem nesse cenário porque um Worker tem partes claras: um fetch handler, um wrangler.toml e bindings explícitos como KV, D1 e R2. As docs oficiais foram revisadas em 3 de junho de 2026; consulte Workers, Wrangler, bindings, KV, D1, R2, Cache API, Rate Limiting e Workers Logs. Para comparar, veja serverless functions e AWS Lambda guide.
O que vamos construir
O exemplo é uma API de pedidos. D1 guarda pedidos, KV lê configurações simples, R2 armazena recibos JSON, Cache API acelera GETs seguros, Rate Limiting controla abuso, Workers Logs guarda logs estruturados e toda resposta recebe headers de segurança.
flowchart LR
Client["Cliente"] --> Worker["Worker fetch handler"]
Worker --> D1["D1 pedidos"]
Worker --> KV["KV configs"]
Worker --> R2["R2 recibos"]
Worker --> Cache["Cache API"]
Worker --> Logs["Workers Logs"]
Um binding é uma capacidade externa injetada no Worker. Se wrangler.toml declara binding = "DB", o código acessa env.DB.
Prompt para Claude Code
Implemente uma API de pedidos com Cloudflare Workers + TypeScript.
Arquivos:
- src/index.ts
- wrangler.toml
- schema.sql
Requisitos:
- Usar module fetch handler
- GET /health retorna JSON
- GET /orders/:id lê D1 e cacheia apenas saída pública segura por 30 segundos
- POST /orders valida JSON e insere no D1
- Validar Authorization Bearer contra API_TOKEN
- Usar SETTINGS KV, DB D1, RECEIPTS R2 e API_RATE_LIMITER
- Adicionar security headers a toda resposta
- Registrar logs como objetos JSON
- Incluir comandos curl de verificação
Não faça:
- Escrever secrets no wrangler.toml
- Assumir servidor Node.js persistente
- Entregar pseudocódigo
Setup com Wrangler
npm create cloudflare@latest claude-worker-api -- --type=hello-world
cd claude-worker-api
npm install -D typescript wrangler
npx wrangler --version
C3 pode gerar wrangler.jsonc em projetos novos. Wrangler aceita JSON/JSONC e TOML; este artigo usa TOML por legibilidade. Se o seu projeto já tem wrangler.jsonc, mantenha as mesmas chaves em 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
Schema 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 executável
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>;
Teste local e 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
Casos de uso
Primeiro, formulários e pedidos. D1 guarda estado, R2 guarda recibos ou anexos e Workers Logs ajuda na operação.
Segundo, receptor de webhooks. Valide assinatura, salve o event ID em D1 e ignore duplicatas.
Terceiro, BFF, ou Backend for Frontend. O Worker esconde API keys, adapta respostas para a UI e cacheia apenas dados seguros.
Quarto, entrega controlada de arquivos via R2. O arquivo pesado fica no R2; autorização e logs ficam no Worker.
Armadilhas
Não escreva Workers como servidores Node.js persistentes. Use Web APIs, Request, Response, fetch, crypto e bindings.
Não misture papéis de storage. KV é para chave-valor pequeno, D1 para dados relacionais, R2 para objetos e Cache API para respostas curtas.
Não cacheie respostas privadas. Se dependem de cookie, token, email ou usuário, evite cache compartilhado.
Não faça rate limit só por IP. Prefira API key, user ID ou tenant ID.
Escolha da plataforma
Use Workers para APIs HTTP de baixa latência perto do cache e dos bindings da Cloudflare. Use Pages Functions quando um site Cloudflare Pages precisa de pouca lógica dinâmica. Use Cloud Run para contêineres, tarefas longas ou frameworks completos. Use Lambda quando o fluxo gira em torno de AWS S3, DynamoDB, EventBridge ou SQS.
Prompt de revisão
Revise esta implementação de Cloudflare Workers.
Verifique:
- compatibilidade com Workers runtime
- alinhamento entre Env e bindings do Wrangler
- vazamento de secrets em código, logs ou config
- uso de bind no D1 e risco de SQL injection
- uso inseguro da Cache API
- chave de Rate Limiting
- security headers em toda resposta
- passos curl ausentes
Retorne achados por severidade, correções e testes.
CTA e nota de verificação
Migre primeiro apenas um endpoint: /health, um GET somente leitura ou um webhook. Dê ao Claude Code arquivos, bindings, comandos de verificação e proibições. Para templates reutilizáveis, veja products; para treinar o time em revisão e operação, veja training.
Resultado do teste prático: Eu conectei o fluxo do artigo como Wrangler, D1, Worker e curl; antes de produção, valide wrangler deploy, wrangler tail, headers, cache e resposta 429 na sua conta Cloudflare.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Workflow Obsidian para CLAUDE.md com Claude Code
Transforme notas de trabalho do Obsidian em notas operacionais CLAUDE.md para preservar contexto.
Claude Code Revenue CTA Routing: artigos para PDF, Gumroad e consultoria
Um fluxo com Claude Code para levar leitores ao PDF grátis, Gumroad ou consultoria conforme intenção.
Regras de handoff para equipes com Claude Code: evidências, permissões, rollback e receita
Formato prático para entregar trabalho do Claude Code com prova, permissões, rollback, PDF grátis, Gumroad e consultoria.