El fetch handler: el corazón de todo Worker
Un Worker es, en su núcleo, una función que recibe una Request y devuelve una Response. El fetch handler es ese punto de entrada, el contrato irreducible sobre el que se apoya todo lo demás en la plataforma.
Quita todas las capas de la plataforma —el enrutado, el almacenamiento, la IA, los bindings— y en el fondo de un Worker encontrarás una sola cosa: una función que recibe una Request y devuelve una Response. Esa función es el fetch handler, y entenderla como el contrato irreducible de todo Worker es el primer salto conceptual del track.
- Qué es el
fetchhandler y por qué es el único punto de entrada. - El contrato
Requestentra,Responsesale, y por qué es asíncrono. - Cómo se apoya en el estándar web
Fetchde la WHATWG. - Por qué todo lo demás —routing, frameworks— se reduce a este contrato.
El punto de entrada
Cuando una petición HTTP llega a tu Worker, el runtime no ejecuta tu archivo de arriba abajo como un script suelto. Busca un handler concreto y lo invoca. Para el tráfico HTTP ese handler se llama fetch, y es la función que la plataforma llama —una vez por cada petición— pasándote la petición entrante y esperando que devuelvas la respuesta.
Esa es la totalidad del contrato. No hay un bucle de servidor que tú mantengas, no hay un listen sobre un puerto, no hay conexiones que aceptar a mano. El runtime posee el servidor; tú solo aportas la función que decide qué responder.
export default {
async fetch(request: Request): Promise<Response> {
return new Response("Hola desde el edge");
},
};
Ese fetch es el corazón. Todo Worker que sirva HTTP —por trivial o por enorme que sea— tiene, en su base, exactamente esta forma.
El runtime invoca fetch una vez por cada petición, y puede tener muchas invocaciones vivas en paralelo si llegan muchas peticiones a la vez. Tu handler no es un bucle que consume una cola: es una función que se ejecuta, entera, para atender una sola petición, y termina cuando devuelve la respuesta. Existen otros handlers hermanos para otros eventos —scheduled para cron, queue para colas, email para correo—, pero fetch es el de HTTP y el que domina este track.
Los handlers que puede exportar un Worker, según el evento que atienden:
fetch— peticiones HTTP entrantes; el protagonista de este track.scheduled— ejecuciones programadas por cron, sin nadie que pida nada.queue— mensajes que llegan de una cola para procesarse en lote.email— correo entrante enrutado hacia tu Worker.
Todos comparten la forma: una función que recibe un evento junto a tus env y ctx. El fetch es solo el más común, y el primero que hay que dominar.
Request entra, Response sale
El tipo del handler, expresado con precisión, es una función de una Request a una Response, o a una promesa de Response:
type FetchHandler = (
request: Request,
) => Response | Promise<Response>;
La Request es un objeto que describe la petición entrante: su método, su URL, sus cabeceras y su cuerpo. La Response es el objeto que construyes para contestar: un estado, unas cabeceras y un cuerpo. No mutas la petición para responder; creas una respuesta nueva y la devuelves. Ese flujo en un solo sentido —entra una cosa, sale otra— es lo que hace que un Worker sea tan fácil de razonar.
Request
Lo que entra: request.method, request.url, request.headers y, si lo hay, request.body. La lees, no la reescribes.
Response
Lo que sale: la construyes con new Response(body, init), donde init fija status y headers. Es tu única forma de contestar.
flowchart LR Cliente -->|Request| Worker Worker -->|Response| Cliente Worker --> Logica[Tu logica dentro del fetch]
La Response admite mucho más que una cadena de texto. Su segundo argumento, el objeto init, fija el estado y las cabeceras; y su cuerpo puede ser texto, JSON serializado, datos binarios o incluso un flujo:
return new Response(JSON.stringify({ ok: true }), {
status: 201,
headers: { "Content-Type": "application/json" },
});
Del lado de la entrada, la Request te expone todo lo que necesitas para decidir qué responder: el método, la URL, las cabeceras y —si lo hay— el cuerpo. Interrogarla es directo:
export default {
async fetch(request: Request): Promise<Response> {
const metodo = request.method; // "GET", "POST"...
const idioma = request.headers.get("Accept-Language");
const cuerpo = metodo === "POST" ? await request.text() : null;
return new Response(`Metodo ${metodo}, idioma ${idioma ?? "?"}`);
},
};
Fíjate en que nunca escribes sobre request: la lees. La forma del contrato —consultar la entrada, fabricar la salida— se respeta hasta en los detalles, y por eso un Worker se razona como una función y no como un objeto con estado mutable.
El cuerpo de una Request es un flujo y, como tal, se lee una única vez. Si haces await request.text() y luego intentas await request.json(), el segundo fallará porque el flujo ya se agotó. Cuando necesites leerlo dos veces, clona la petición con request.clone() antes de consumirla. Es un detalle que atrapa a todo el mundo la primera vez.
Asíncrono por diseño
Fíjate en el async y en el Promise<Response> de la firma. No es decorativo: casi todo lo interesante que hará tu Worker —leer de KV, consultar D1, llamar a otra API con fetch— es asíncrono. Por eso el contrato admite que devuelvas una promesa, y el runtime la espera por ti antes de enviar la respuesta al cliente.
export default {
async fetch(request: Request): Promise<Response> {
const datos = await fetch("https://api.example.com/hora");
const cuerpo = await datos.text();
return new Response(cuerpo);
},
};
Un Worker que solo devuelve texto estático podría ser síncrono, pero en la práctica escribirás async fetch casi siempre. Devolver una promesa no bloquea nada: mientras tu Worker espera esa subpetición, el isolate queda libre para atender a otros.
Hay un matiz de rendimiento que conviene sembrar ya: puedes devolver la Response antes de que su cuerpo esté completo. Si el cuerpo es un ReadableStream, el runtime empieza a enviar bytes al cliente mientras tú los sigues produciendo. La promesa que devuelve tu handler resuelve la cabecera de la respuesta; el cuerpo puede seguir fluyendo después. Es justo lo que permite hacer streaming de una respuesta de IA token a token sin esperar a tenerla entera.
La palabra fetch aparece en dos sitios y conviene no confundirlos. Tu handler se llama fetch porque atiende peticiones entrantes. La función global fetch(...) que llamas dentro es la que hace peticiones salientes hacia otros servicios. Mismo nombre, direcciones opuestas: una recibe, la otra envía.
Es el estándar web, no una invención
Request y Response no son tipos propios de Cloudflare. Son las interfaces definidas por el estándar Fetch de la WHATWG, las mismas que ya existen en el navegador y en los Service Workers. El handler fetch de un Worker es, conceptualmente, el FetchEvent del Service Worker llevado al servidor.
Esto tiene una consecuencia práctica enorme: lo que aprendes aquí es transferible. Sabes leer request.headers, construir una Response con un status y un Content-Type, manejar un ReadableStream como cuerpo —y ese conocimiento vale igual en el navegador, en Deno o en Bun. Cloudflare apuesta por los estándares web precisamente para que tu código no quede atrapado en su plataforma.
No es un detalle menor de portabilidad, sino una decisión de diseño con peso estratégico: al elegir las APIs del estándar en vez de inventar las suyas, la plataforma te promete que la inversión en aprender su modelo no caduca con ella. Escribes contra la web, no contra un proveedor.
El fetch handler es tu código. El runtime —quien recibe la conexión TCP, parsea el HTTP, construye el objeto Request, invoca tu función y serializa tu Response de vuelta a bytes en el cable— es de la plataforma. Tú vives en un nivel de abstracción cómodo: piensas en objetos Request y Response, no en sockets ni en cabeceras crudas. Esa frontera limpia es lo que te deja concentrarte en la lógica.
Todo lo que verás en este track —enrutar por rutas, middleware, frameworks como Hono, servir assets, hablar con D1 o con Workers AI— se construye encima de una única abstracción que no cambia: una función que toma una Request y produce una Response. Un framework de routing no es más que código que, dado un Request, elige qué Response fabricar; el middleware es una Response que pasa por varias manos antes de salir; un endpoint de IA es una Response cuyo cuerpo lo genera un modelo. Cuando internalizas que el fetch handler es el átomo indivisible de la plataforma, dejas de ver productos de Cloudflare sueltos y empiezas a ver transformaciones de peticiones en respuestas. Esa es la lente que convierte una lista de features en un modelo mental coherente: da igual cuántas capas apiles encima, en el fondo siempre hay una Request entrando y una Response saliendo, y tú controlas exactamente la función que hay en medio.
- Escribe la firma del
fetchhandler de memoria, con sus tipos: deRequestaPromise<Response>. - Piensa en un endpoint real que uses a diario, como un login o un buscador. Descríbelo en una frase con la forma “dado este
Request, devuelve estaResponse”. - Abre la documentación del estándar
Fetchde la WHATWG y localiza las interfacesRequestyResponse: comprueba que son las mismas que usa el navegador. - Explica en voz alta por qué un Worker no necesita un
listensobre un puerto como un servidor de Node.