CloudflareMódulo I · Fundamentos · Capítulo 3 de 15Nivel: BásicoEn desarrollo

3. Tu primer Worker

Cómo crear un Worker en TypeScript 7, generar los tipos de los bindings, separar variables y secretos, desarrollar en local y escribir pruebas.

En desarrollo. La publico antes de terminarla porque ya puede servirte; la iré completando y anotaré los cambios al final.

Víctor Rodríguez6 min de lectura

Probado conTypeScript 7.0Wrangler 4.157Vitest 5.1

Un Worker conectado con enchufes a sus servicios: base de datos, archivos y claves.

Qué vas a aprender

  • Crear tu primer programa en Cloudflare y verlo funcionar en tu ordenador.
  • Conectarlo con otros servicios sin escribir contraseñas en el código.
  • Comprobar que funciona con pruebas automáticas.
  • Publicarlo en internet con un solo comando.

Conocimientos iniciales

Si alguna de estas preguntas no te suena, ábrela: está explicada desde cero.

  • ¿Qué es la terminal?

    La ventana donde escribes órdenes al ordenador en lugar de pulsar botones (en Windows, PowerShell o Terminal; en Mac, Terminal). En esta guía la usamos para crear el proyecto, probarlo y publicarlo.

  • ¿Qué es TypeScript?

    JavaScript con tipos: le dices qué forma tiene cada dato y el compilador avisa de errores antes de ejecutar el código. Toda la guía usa TypeScript 7.

  • ¿Qué es una API?

    La puerta por la que un programa pide cosas a otro. Una API HTTP es una lista de direcciones (/api/notas) que devuelven datos, normalmente en JSON, en lugar de páginas.

  • ¿Qué es un Worker?

    Un trozo de tu código que Cloudflare ejecuta en sus servidores cada vez que llega una petición. Arranca en milisegundos, no tienes que mantener ninguna máquina y se paga por uso.

  • ¿Qué es Wrangler?

    La herramienta de terminal de Cloudflare para Workers: crea proyectos, los ejecuta en tu ordenador, genera tipos y los publica (despliega).

  • ¿Qué es un binding?

    La conexión entre tu Worker y otro servicio de Cloudflare (una base de datos, un almacén de archivos). Se declara en wrangler.jsonc y aparece en tu código como env.NOMBRE, sin contraseñas ni direcciones.

  • ¿Qué diferencia hay entre una variable y un secreto?

    Valores de configuración que no van escritos en el código. Una variable (el nombre del entorno) puede estar en la configuración; un secreto (una contraseña, una clave de API) se guarda aparte y nunca se sube al repositorio.

  • ¿Qué es desplegar?

    Publicar una versión de tu aplicación para que la usen otros. En Cloudflare, un comando (o un cambio en GitHub) sube tu código y en segundos está en producción.

En este capítulo monto el esqueleto que uso en todos mis proyectos: un Worker en TypeScript 7, con un enrutador, tipos generados para los bindings, secretos fuera del código y pruebas que se ejecutan en el mismo runtime que producción.

Para quién es y qué necesitas

Para quien no ha escrito nunca un Worker. Si ya tienes uno funcionando, salta a Bindings.

Antes de empezar:

  • Node.js 22 o superior.
  • Una cuenta de Cloudflare y la sesión iniciada con npx wrangler login.
  • Saber qué es una API HTTP. No hace falta saber nada de Workers.

Al terminar tendrás un Worker en TypeScript 7 con un enrutador, tipos generados para los bindings, secretos fuera del código, pruebas que se ejecutan en el mismo runtime que producción y tu primer despliegue.

Qué es un Worker por dentro

Un Worker es un módulo de JavaScript que exporta handlers: funciones que Cloudflare llama cuando pasa algo. El más común es fetch, que recibe una petición HTTP y devuelve una respuesta.

flowchart LR
    P[Petición HTTP] --> F[fetch]
    C[Cron] --> S[scheduled]
    Q[Mensaje de cola] --> QU[queue]
    E[Correo entrante] --> EM[email]
    F & S & QU & EM --> ENV[env: bindings<br/>DB, ATTACHMENTS, AI...]

Cada handler recibe env, un objeto con tus bindings (base de datos, bucket, cola, variables y secretos), y ctx, que permite, por ejemplo, seguir trabajando después de responder con ctx.waitUntil().

Wrangler o cf: qué CLI usar

Cloudflare tiene dos herramientas de línea de comandos:

Wrangler cf
Estado Estable, la de siempre Beta (noviembre de 2026)
Configuración wrangler.jsonc cloudflare.config.ts (con autocompletado)
Alcance Workers y sus recursos Toda la API de Cloudflare (DNS, zonas, seguridad…)

En esta guía uso Wrangler, porque es estable y lo que encontrarás en casi toda la documentación. Si empiezas un proyecto nuevo y te apetece probar cf, la conversión es cf migrate, y los comandos equivalentes se buscan con cf cli search "<task>".

Crear el proyecto

Terminal
npm create cloudflare@latest notes -- --type=hello-world --lang=ts
cd notes
npm install -D typescript@7 hono

C3 (create cloudflare) crea esta estructura:

notes/
├── src/index.ts ← your Worker
├── test/ ← tests
├── wrangler.jsonc ← configuration and bindings
├── worker-configuration.d.ts ← generated types (do not edit)
├── tsconfig.json
└── package.json

Configurar TypeScript 7

Wrangler empaqueta tu código con esbuild, que quita los tipos sin comprobarlos. La comprobación la hace TypeScript por separado. Por eso el tsconfig.json es de “solo comprobar”:

tsconfig.json
{
"compilerOptions": {
"target": "es2024",
"lib": ["es2024"],
"module": "preserve",
"moduleResolution": "bundler",
"strict": true,
"noEmit": true,
"isolatedModules": true,
"skipLibCheck": true,
"noUncheckedIndexedAccess": true
},
"include": ["worker-configuration.d.ts", "src", "test"]
}
package.json (scripts)
{
"scripts": {
"dev": "wrangler dev",
"types": "wrangler types",
"check": "tsc",
"test": "vitest run",
"deploy": "npm run check && wrangler deploy"
}
}

El primer Worker con un enrutador

Para una API con varias rutas uso Hono: es pequeño, está pensado para Workers y tipa los bindings.

src/index.ts
import { Hono } from "hono";
const app = new Hono<{ Bindings: Env }>();
app.get("/api/health", (c) => c.json({ ok: true }));
app.get("/api/hello/:name", (c) => {
const name = c.req.param("name");
return c.json({ message: `Hello, ${name}` });
});
app.notFound((c) => c.json({ error: "Not found" }, 404));
app.onError((err, c) => {
console.error(err);
return c.json({ error: "Something went wrong" }, 500);
});
export default app;

Si no quieres dependencias, un Worker sin enrutador es así:

src/index.ts
export default {
async fetch(request, env, ctx): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/api/health") {
return Response.json({ ok: true });
}
return new Response("Not found", { status: 404 });
},
} satisfies ExportedHandler<Env>;

satisfies ExportedHandler<Env> da tipos a request, env y ctx sin escribirlos.

Bindings: conectar el Worker con servicios

Un binding se declara en wrangler.jsonc y aparece en env. Así queda la configuración que iremos completando en los próximos capítulos:

wrangler.jsonc
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "notes",
"main": "src/index.ts",
"compatibility_date": "2026-11-03",
"observability": { "enabled": true },
"vars": {
"ENVIRONMENT": "production",
"PUBLIC_URL": "https://notes.mydomain.dev"
},
"d1_databases": [
{ "binding": "DB", "database_name": "notes", "database_id": "…" }
],
"r2_buckets": [
{ "binding": "ATTACHMENTS", "bucket_name": "notes-attachments" }
]
}

Después de tocar los bindings, regenera los tipos:

Terminal
npx wrangler types

Wrangler escribe worker-configuration.d.ts con la interfaz Env (y los tipos del runtime para tu compatibility_date):

worker-configuration.d.ts (generado, extracto)
interface Env {
ENVIRONMENT: "production";
PUBLIC_URL: "https://notes.mydomain.dev";
DB: D1Database;
ATTACHMENTS: R2Bucket;
}

Variables y secretos

Lo que más me ayudó a entenderlo fue dibujar dónde vive cada cosa:

flowchart LR
    subgraph REPO[En el repositorio]
      V[wrangler.jsonc<br/>vars: ENVIRONMENT, PUBLIC_URL]
    end
    subgraph FUERA[Fuera del repositorio]
      DV[.dev.vars<br/>secretos en tu máquina]
      SP[wrangler secret put<br/>secretos en producción]
    end
    V --> E[env en tu Worker]
    DV -->|wrangler dev| E
    SP -->|desplegado| E
Qué Dónde Ejemplo Visible en el panel
Configuración no sensible vars en wrangler.jsonc URL pública, nombre del entorno Sí
Secretos wrangler secret put Claves de API, tokens de Stripe No (solo el nombre)
Secretos en local .dev.vars (en .gitignore) Los mismos, con valores de prueba —
Secretos compartidos entre Workers Secrets Store Una clave usada por varios servicios No
Terminal
npx wrangler secret put STRIPE_KEY
.dev.vars
STRIPE_KEY=sk_test_xxx

Los secretos aparecen en env igual que las variables. wrangler types añade a Env los nombres que encuentra en .dev.vars, así que basta con tenerlos ahí con un valor de prueba.

Desarrollar en local

Terminal
npm run dev

wrangler dev levanta tu Worker en http://localhost:8787 con el mismo runtime de producción (workerd) y simula en local D1, KV, R2, Queues y Durable Objects. Los datos locales se guardan en .wrangler/state y no tocan producción.

Necesidad Cómo
Trabajar sin conexión con datos de prueba Por defecto: todo local
Usar un recurso real (por ejemplo, Workers AI, que no tiene simulación) "remote": true en ese binding
Probar con datos reales de una base de datos Mejor no: copia los datos a local con wrangler d1 export

Escribir pruebas en el runtime de Workers

Las pruebas se ejecutan con Vitest dentro de workerd, así que usan los mismos bindings simulados. El paquete es @cloudflare/vitest-plugin; antes se llamaba @cloudflare/vitest-pool-workers y se configuraba de otra forma, así que si ves defineWorkersConfig en un tutorial, es la versión antigua.

Terminal
npm install -D vitest @cloudflare/vitest-plugin
vitest.config.ts
import { cloudflareTest } from "@cloudflare/vitest-plugin";
import { defineConfig } from "vitest/config";
export default defineConfig({
plugins: [
cloudflareTest({
wrangler: { configPath: "./wrangler.jsonc" },
// Tests never touch real resources, even if a binding has "remote": true.
remoteBindings: false,
}),
],
});
test/health.test.ts
import { exports } from "cloudflare:workers";
import { describe, expect, it } from "vitest";
describe("API", () => {
it("responds on /api/health", async () => {
const res = await exports.default.fetch("https://notes.test/api/health");
expect(res.status).toBe(200);
expect(await res.json()).toEqual({ ok: true });
});
});

env y exports se importan de cloudflare:workers, igual que en el código del Worker, y exports.default.fetch() llama a tu Worker sin pasar por la red. SELF y env de cloudflare:test siguen funcionando, pero están obsoletos.

Desplegar

Terminal
npm run deploy

El primer despliegue te da una URL notes.<your-subdomain>.workers.dev. Cada despliegue crea una versión nueva; cómo gestionarlas está en Entornos y despliegues.

Los límites que hay que conocer

Límite Plan gratuito Plan de pago
CPU por petición 10 ms 30 s por defecto, hasta 5 min
Memoria 128 MB 128 MB
Subpeticiones (fetch, bindings) 50 10.000
Tamaño del Worker 64 MiB 64 MiB
Workers por cuenta 100 500
Duración de una petición HTTP Sin límite mientras el cliente siga conectado Igual

Cuánto cuesta

Cuánto cuesta al mes

Supuestos: Una API con 2 millones de peticiones al mes y 2 ms de CPU de media

ConceptoImporte
Plan de pago de WorkersPor cuenta. Incluye 10 M de peticiones y 30 M de ms de CPU5,00 $
Peticiones2 M, dentro de lo incluido0,00 $
CPU4 M de ms, dentro de lo incluido0,00 $
Total al mes5,00 $

Precios consultados el 3 de noviembre de 2026. Pueden cambiar.

Con ese tráfico cabe también en el plan gratuito (son unas 67.000 peticiones al día). Pasarse a pago compensa por los límites de CPU, las métricas de 7 días y productos que solo existen en pago.

Resumen

  • Un Worker exporta handlers; los servicios llegan por env como bindings tipados con wrangler types.
  • TypeScript 7 comprueba; Wrangler empaqueta. Pon tsc antes de cada despliegue.
  • Variables en la configuración, secretos con wrangler secret put y .dev.vars en local.
  • Siguiente capítulo: servir el frontend con Static Assets.

Recursos

¿Te ha servido? Esto forma parte de un libro que estoy escribiendo: Cloudflare de cero a producción. Ver el índice provisional →