Bindings dentro del framework: alcanzar env
Un framework no te quita los bindings: te los reexpone a través de su contexto. Dónde vive env en Astro, React Router, SvelteKit y Hono, cómo tiparlo con wrangler types para no perder el autocompletado, y por qué las reglas del env crudo siguen vigentes aunque medie un framework.
La pregunta que todo el mundo se hace al llevar un framework a Workers es la misma: “vale, pero ¿cómo llego a mi base de datos D1, a mi KV, a Workers AI?”. La respuesta tranquiliza: el framework no te esconde los bindings, te los entrega por su propia puerta. El objeto env que conociste en un Worker crudo sigue ahí, íntegro; solo cambia el lugar donde lo recoges. Aprender ese lugar en cada framework, y tiparlo bien, es todo lo que separa una app full-stack del acceso pleno a la plataforma.
- Localizar
envdentro del contexto de cada framework. - Acceder a D1, KV y Workers AI desde una ruta de servidor del framework.
- Tipar los bindings con
wrangler typespara conservar el autocompletado. - Confirmar que las reglas del
envcrudo siguen aplicando bajo el framework.
El mismo env, muchas puertas
En un Worker crudo, env llega como segundo argumento del fetch handler. Cuando media un framework, ese argumento no desaparece: el adaptador lo captura y lo deposita en el objeto de contexto que el framework entrega a tus rutas de servidor. Por eso el acceso cambia de sintaxis pero no de fondo. Cada framework elige un nombre para esa puerta, y merece la pena tenerlos juntos:
Hono
Lo tienes en c.env. Tipas la app con new Hono<{ Bindings: Env }>() y accedes con c.env.DB o c.env.AI.
Astro
Vive en Astro.locals.runtime.env en páginas, y en context.locals.runtime.env dentro de los endpoints de API.
React Router
Llega por el context de loader y action, en context.cloudflare.env, el sucesor del patrón de Remix.
SvelteKit
Aparece en event.platform.env dentro de load, acciones de formulario y endpoints del servidor.
Cuatro nombres, un solo objeto. Interiorizar que c.env, locals.runtime.env, context.cloudflare.env y platform.env son alias de la misma cosa te ahorra tratar cada framework como un mundo aparte. La plataforma es una; el framework solo decide en qué percha cuelga el llavero.
Acceder a D1, KV y AI
Con la puerta localizada, el uso de cada binding es idéntico al de un Worker crudo, porque los objetos que hay dentro de env son exactamente los mismos clientes vivos. Un loader de React Router que consulta D1, lee de KV e invoca Workers AI se ve así:
export async function loader({ context }: LoaderFunctionArgs) {
const { env } = context.cloudflare;
const usuario = await env.DB
.prepare("SELECT * FROM usuarios WHERE id = ?")
.bind(1)
.first();
const preferencias = await env.KV.get("prefs:1");
const resumen = await env.AI.run("@cf/meta/llama-3.1-8b-instruct", {
prompt: "Resume el perfil del usuario en una linea",
});
return { usuario, preferencias, resumen };
}
El mismo trío en SvelteKit apenas cambia la puerta de entrada; los métodos prepare, get y run son idénticos porque los clientes lo son:
export async function load({ platform }) {
const { results } = await platform.env.DB.prepare("SELECT * FROM posts").all();
const cache = await platform.env.KV.get("home");
return { posts: results, cache };
}
Y en un endpoint de Astro, la puerta es locals.runtime.env, pero lo que hay detrás vuelve a ser exactamente el mismo cliente de KV:
export async function GET({ locals }) {
const { env } = locals.runtime;
const banner = await env.KV.get("banner");
return new Response(banner ?? "");
}
Compara los tres bloques y verás que la única línea que cambia entre frameworks es la que extrae env del contexto. A partir de ahí, prepare, bind, first, all, get y run son idénticos, porque los objetos que viven en env no saben ni les importa qué framework los invoca. Aprender esos métodos una vez basta para los cuatro.
El framework no inventa una forma nueva de declarar bindings. Los sigues definiendo en wrangler.jsonc con sus bloques d1_databases, kv_namespaces y ai, exactamente como en un Worker sin framework. El adaptador lee esa configuración y se encarga de que los recursos declarados aparezcan en el env que expone su contexto. La declaración vive en la plataforma; el framework solo la consume.
Tipar para no ir a ciegas
Acceder a env sin tipos funciona, pero pierdes el autocompletado y la comprobación de errores, que es media razón de usar TypeScript. La herramienta canónica es wrangler types: lee tu wrangler.jsonc y genera una interfaz Env con cada binding y su tipo correcto, que luego conectas al contexto de tu framework.
wrangler types # genera worker-configuration.d.ts con la interfaz Env
Cada framework tiene su forma de enganchar esa interfaz a su contexto —Astro y SvelteKit exponen tipos de runtime y platform que puedes extender con tu Env, y en Hono la pasas como parámetro genérico—, pero el origen de la verdad es siempre el mismo archivo generado. Regenéralo cada vez que añadas un binding y el editor te guiará por env como si fuera parte del lenguaje.
El descuido más común es dejar que ese archivo envejezca. Como no se regenera solo, si declaras un binding nuevo en wrangler.jsonc y olvidas volver a ejecutar wrangler types, el editor seguirá creyendo que no existe y te marcará un error donde no lo hay. La costumbre sana es encadenar la generación de tipos a un script previo al build o al arranque del entorno, de modo que los tipos nunca se separen de la configuración real que describe tus recursos.
flowchart LR CFG[wrangler.jsonc con bindings] --> WT[wrangler types genera Env] CFG --> AD[el adaptador conecta env al contexto] WT --> CTX[contexto del framework tipado] AD --> CTX CTX --> USE[c.env / locals.runtime.env / platform.env] style CTX fill:#89b4fa,color:#11111b style USE fill:#a6e3a1,color:#11111b
Las reglas no cambian
Que un framework medie no deroga ninguna de las leyes del env que aprendiste con el Worker crudo. Siguen todas en pie, y olvidarlo produce bugs sutiles que el framework hace más difíciles de ver:
enves estable por isolate y compartido entre peticiones: no guardes en él estado de una petición concreta.- No hay entrada y salida fuera del contexto de una petición: no llames a
env.DBni aenv.KVen el ámbito de módulo del framework, solo dentro de unloader, unloado un endpoint. - Los bindings de datos son clientes vivos, no cadenas de texto:
env.DBes unD1Database, no una URL de conexión. - El acceso no cuesta reconexión: leer un binding cuantas veces quieras es gratis, porque ya está resuelto.
La lección honda de este nivel es que la inyección de dependencias que define a la plataforma sobrevive intacta al framework, y esa continuidad es una elección de diseño deliberada, no un accidente. En un Worker crudo, tu código no busca sus dependencias: las recibe ya resueltas en env, un llavero que la plataforma construye y te entrega. Un framework mal diseñado podría haber roto esa propiedad, obligándote a configurar clientes a mano, a leer variables sueltas o a montar tu propio descubrimiento de servicios. Los adaptadores de Cloudflare hacen justo lo contrario: toman el env inyectado y lo reexponen sin adulterarlo a través del contexto del framework, de modo que la virtud original se conserva. Sigues declarando qué necesitas —los bindings en wrangler.jsonc— y sigues recibiéndolo ya listo, ahora en c.env o en platform.env en vez de en el segundo argumento de fetch. Que los nombres cambien y el mecanismo no es la señal de que estás ante una buena capa de abstracción: la ergonomía se mueve, el contrato permanece. Por eso aprender los bindings una sola vez, a fondo, en el Worker desnudo, te habilita para usarlos en cualquier framework: no memorizas cuatro sistemas distintos, sino un sistema con cuatro fachadas. Y por eso las reglas del env —su alcance por isolate, su prohibición de I/O en el arranque, su naturaleza de solo lectura para el estado de petición— siguen gobernando bajo el framework con la misma severidad. El framework te da comodidad para tocar la plataforma; no reescribe las leyes de la plataforma. Quien confunde la fachada con el sistema tropieza; quien ve el env inyectado detrás de cada contexto camina firme en los cuatro frameworks a la vez.
- Declara un binding de D1 y otro de KV en
wrangler.jsoncy ejecutawrangler types; abre el archivo generado y localiza la interfazEnv. - En tu framework, escribe una ruta de servidor que lea de D1 y de KV a través de su contexto —
c.env,locals.runtime.envoplatform.env— y confirma que el editor te autocompleta los bindings. - Invoca Workers AI con
env.AI.run(...)desde esa misma ruta y devuelve el resultado en la respuesta. - Intenta acceder a
env.DBen el ámbito de módulo del framework, fuera de cualquier ruta, y razona a partir del error por qué el I/O exige el contexto de una petición.