Use Cases (Actualizado: 3/6/2026)

Cloudflare Workers con Claude Code: guía práctica de API edge

Crea una API Workers con Claude Code, Wrangler, KV/D1/R2, caché, rate limiting, logs y cabeceras seguras.

Cloudflare Workers con Claude Code: guía práctica de API edge

Cloudflare Workers ejecuta JavaScript y TypeScript en la red edge de Cloudflare. Edge significa que el código se ejecuta cerca del usuario, no solo en una región central. Es una buena opción para APIs pequeñas, webhooks, BFF, respuestas cacheadas, validaciones de seguridad y control de descargas desde R2.

Claude Code encaja bien porque un Worker se divide en piezas claras: un fetch handler, un wrangler.toml y bindings explícitos como KV, D1 y R2. Revisé la documentación oficial el 3 de junio de 2026; ten a mano Workers, Wrangler, bindings, KV, D1, R2, Cache API, Rate Limiting y Workers Logs. Como comparación interna, revisa serverless functions y AWS Lambda guide.

Qué vamos a construir

El ejemplo es una API de pedidos. Guarda datos en D1, lee configuración desde KV, escribe recibos en R2, cachea respuestas GET seguras, limita peticiones autenticadas, registra logs estructurados y añade cabeceras de seguridad.

flowchart LR
  Client["Cliente"] --> Worker["Worker fetch handler"]
  Worker --> D1["D1 pedidos"]
  Worker --> KV["KV configuración"]
  Worker --> R2["R2 recibos"]
  Worker --> Cache["Cache API"]
  Worker --> Logs["Workers Logs"]

Un binding es una capacidad externa inyectada en el Worker. Si declaras binding = "DB", el código la usa como env.DB.

Prompt inicial para Claude Code

Implementa una API de pedidos con Cloudflare Workers + TypeScript.

Archivos:
- src/index.ts
- wrangler.toml
- schema.sql

Requisitos:
- Usar module fetch handler
- GET /health devuelve JSON
- GET /orders/:id lee D1 y cachea solo salida pública segura por 30 segundos
- POST /orders valida JSON e inserta en D1
- Validar Authorization Bearer contra API_TOKEN
- Usar SETTINGS KV, DB D1, RECEIPTS R2 y API_RATE_LIMITER
- Añadir security headers a cada respuesta
- Registrar logs como objetos JSON
- Incluir comandos curl de verificación

Prohibido:
- Escribir secrets en wrangler.toml
- Asumir un servidor Node.js persistente
- Entregar pseudocódigo

Configuración con Wrangler

npm create cloudflare@latest claude-worker-api -- --type=hello-world
cd claude-worker-api
npm install -D typescript wrangler
npx wrangler --version

C3 puede generar wrangler.jsonc en proyectos nuevos. Wrangler admite JSON/JSONC y TOML; este artículo usa TOML por legibilidad. Si tu proyecto ya tiene wrangler.jsonc, conserva las mismas claves en 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

Esquema 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 ejecutable

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>;

Prueba local y despliegue

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 reales

Primero, formularios y pedidos. D1 guarda el estado, R2 guarda recibos o adjuntos y Workers Logs conserva evidencia operativa.

Segundo, recepción de webhooks. Verifica la firma, guarda el event ID en D1 y evita procesar duplicados.

Tercero, BFF: Backend for Frontend. El Worker oculta API keys, adapta la respuesta para la UI y cachea solo datos seguros.

Cuarto, entrega controlada de archivos desde R2. El objeto pesado vive en R2; autorización, logs y cabeceras viven en el Worker.

Errores comunes

No escribas Workers como si fueran servidores Node.js persistentes. Usa Web APIs, Request, Response, fetch, crypto y bindings.

No mezcles responsabilidades de almacenamiento. KV es para clave-valor pequeño, D1 para datos relacionales, R2 para objetos y Cache API para acelerar respuestas breves.

No caches respuestas privadas. Si dependen de cookie, token, email o usuario, evita caché compartida o diseña una clave y TTL muy estrictos.

No limites solo por IP. Usa API key, user ID o tenant ID cuando sea posible.

Qué plataforma elegir

Elige Workers para APIs HTTP de baja latencia cerca del caché y los bindings de Cloudflare. Elige Pages Functions cuando una web en Cloudflare Pages necesita poca lógica dinámica. Elige Cloud Run si necesitas contenedores, tareas largas o un framework completo. Elige Lambda si el flujo vive en AWS con S3, DynamoDB, EventBridge o SQS.

Prompt de revisión

Revisa esta implementación de Cloudflare Workers.

Comprueba:
- compatibilidad con Workers runtime
- coincidencia entre Env y bindings de wrangler
- fuga de secrets en código, logs o config
- uso de bind en D1 y riesgo de SQL injection
- uso inseguro de Cache API
- clave de Rate Limiting
- security headers en todas las respuestas
- pasos curl faltantes

Devuelve hallazgos por severidad, correcciones y pruebas.

CTA y nota de verificación

Migra primero un solo endpoint: /health, un GET de solo lectura o un webhook. Da a Claude Code archivos, bindings, comandos de verificación y prohibiciones. Para plantillas reutilizables, consulta products; para formar al equipo en revisión y operación, consulta training.

Resultado de probarlo en la práctica: Probé el flujo del artículo como cadena Wrangler, D1, Worker y curl; antes de producción, repite wrangler deploy, wrangler tail, cabeceras, caché y respuesta 429 en tu cuenta.

#Claude Code #Cloudflare Workers #edge computing #serverless #API
Gratis

PDF gratis: cheatsheet de Claude Code

Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.

Cuidamos tus datos y no enviamos spam.

Masa

Sobre el autor

Masa

Ingeniero enfocado en workflows prácticos con Claude Code.