Configurar el almacenamiento: drivers de sesión
Dónde viven de verdad los datos de sesión. El almacenamiento sobre unstorage y su catálogo de drivers: memoria para desarrollo, filesystem para una sola máquina, Workers KV en el edge y Redis para varias instancias. Cómo se registra un driver con los ayudantes de sessionDrivers o con el par entrypoint y config, cuándo cada adapter trae uno por defecto, y qué compromiso de persistencia, latencia y reparto elige cada opción.
La lección anterior dejó una pregunta abierta: cuando Astro.session.set guarda el carrito, ¿dónde acaba ese carrito? La respuesta es el driver: la pieza intercambiable que decide el hogar físico de los datos de sesión. Astro delega ese trabajo en unstorage, una capa de abstracción de almacenamiento con un catálogo de destinos —un objeto en memoria, un archivo en disco, un espacio de claves en el edge, un Redis compartido—. Elegir driver no es un detalle de configuración: es una decisión de arquitectura que fija tres propiedades a la vez —si los datos sobreviven a un reinicio, cuánto tardan en leerse y si varias instancias los comparten—. Cambiarlo es cambiar una línea; entender qué compras con cada uno es el objetivo de esta lección.
- Ver el almacenamiento de sesión como un driver de
unstorageintercambiable. - Registrar un driver con los ayudantes de
sessionDriverso conentrypointyconfig. - Comparar memoria, filesystem, KV y Redis según persistencia, latencia y reparto.
- Saber qué adapters traen un driver por defecto y cuáles lo exigen.
El driver es el hogar físico de los datos
Astro no reinventa el almacenamiento: se apoya en unstorage, una biblioteca que expone una interfaz común —guardar, leer y borrar por clave— sobre decenas de backends distintos. Cada backend es un driver, y todos hablan el mismo dialecto de tres verbos, de modo que Astro puede volcar una sesión en cualquiera de ellos sin conocer sus tripas. Esa uniformidad es la que te deja empezar con un archivo en disco y terminar en un Redis distribuido sin tocar una sola línea de tu código de páginas.
Registrar un driver tiene dos formas. La primera son los ayudantes que Astro exporta desde astro/config bajo sessionDrivers, pensados para los backends más comunes. La segunda, más general, describe el driver como un par entrypoint —el módulo que lo implementa— y config —sus opciones serializables—, lo que permite usar cualquier driver del catálogo de unstorage, aunque Astro no tenga un ayudante dedicado.
// astro.config.mjs — dos maneras de nombrar un driver
import { defineConfig, sessionDrivers } from 'astro/config';
export default defineConfig({
session: {
// 1) ayudante dedicado
driver: sessionDrivers.redis({ url: process.env.REDIS_URL }),
// 2) cualquier driver de unstorage por entrypoint (alternativa)
// driver: { entrypoint: 'unstorage/drivers/fs', config: { base: './.sessions' } },
},
});
El catálogo: memoria, filesystem, KV y Redis
Cada driver ocupa un punto distinto en el espacio de compromisos. No hay un “mejor”: hay el adecuado para tu topología de despliegue.
El driver en memoria guarda las sesiones en una estructura del propio proceso. Es instantáneo y no pide infraestructura, pero paga dos precios: los datos mueren al reiniciar el servidor, y no se comparten entre procesos. Sirve para desarrollo y pruebas, nunca para producción con más de una instancia. El ayudante sessionDrivers.lruCache ofrece justamente eso —un caché en memoria con un tope de entradas— y deja claro su carácter efímero.
session: { driver: sessionDrivers.lruCache({ max: 1000 }) }
El driver de filesystem escribe cada sesión como un archivo en disco. Sobrevive a reinicios y no necesita servicios externos, pero ata las sesiones a una máquina: si despliegas dos instancias detrás de un balanceador, cada una verá sus propios archivos y un usuario perdería la sesión al saltar de una a otra. Es la opción por defecto que instala el adapter de Node, ideal para un servidor único.
// filesystem via unstorage, util en una sola maquina
session: { driver: { entrypoint: 'unstorage/drivers/fs', config: { base: './.sessions' } } }
Workers KV es el almacén clave-valor de Cloudflare, distribuido por el edge. Su fuerte es la lectura global de baja latencia desde cualquier región; su matiz es la consistencia eventual —una escritura tarda un instante en propagarse a todos los nodos—. Es el driver por defecto del adapter de Cloudflare, que lo enlaza a un binding declarado en tu proyecto.
// KV de Cloudflare por binding
session: { driver: { entrypoint: 'unstorage/drivers/cloudflare-kv-binding', config: { binding: 'SESSION' } } }
Redis es el caballo de batalla de las sesiones en producción con varias instancias. Es un almacén en memoria, rapidísimo, accesible por todas tus máquinas a la vez y con expiración por clave nativa —perfecta para una sesión con caducidad—. A cambio, es un servicio que hay que operar o contratar. Cuando tu aplicación escala horizontalmente, Redis (o un KV equivalente) deja de ser un lujo y pasa a ser el requisito para que la sesión sea coherente en toda la flota.
La regla de decisión es topológica, no de gusto. Una sola máquina admite filesystem; varias instancias detrás de un balanceador exigen un almacén compartido —Redis o KV—; el edge distribuido pide KV por su lectura global; y el desarrollo se conforma con memoria. Hay un caso que atrapa a muchos: en una plataforma serverless y sin estado, ni siquiera el filesystem sobrevive entre invocaciones, porque cada una puede correr en una máquina distinta y efímera. Ahí un almacén externo no es una preferencia, es una condición de existencia de la sesión.
No siempre tienes que elegir a mano. El adapter de Node configura un driver de filesystem, el de Cloudflare usa Workers KV y el de Netlify usa Blobs, todos sin que escribas la clave session.driver. Los demás adapters —Vercel, por ejemplo— no asumen un almacén por ti y exigen que nombres uno. Cuando ves que las sesiones “no persisten” en un despliegue, la primera pregunta es qué driver hay debajo: puede que sea el de memoria por defecto del entorno de desarrollo, que no sobrevive a nada.
El catálogo de unstorage es amplio, pero no todos los drivers vienen listos para usar: varios dependen de un paquete cliente que instalas aparte. El driver de Redis, por ejemplo, se apoya en un cliente como ioredis, y algunos backends de nube piden además sus credenciales por variables de entorno. Si un driver falla al arrancar con un error de módulo no encontrado, casi siempre es una dependencia de par sin instalar, no un fallo de configuración de Astro.
flowchart TD API[Astro session set y get] --> UNS[capa unstorage] UNS --> MEM[memoria efimera y por proceso] UNS --> FS[filesystem una sola maquina] UNS --> KV[workers KV en el edge] UNS --> RED[redis compartido entre instancias] style API fill:#89b4fa,color:#11111b style RED fill:#a6e3a1,color:#11111b style MEM fill:#f38ba8,color:#11111b
Configuración en build y configuración en runtime
Hay un detalle que muerde en producción si se ignora. Los drivers se resuelven en tiempo de build, y cualquier variable de entorno usada dentro de la configuración del driver queda incrustada en el artefacto compilado. Si tu REDIS_URL cambia entre entornos, no basta con leerla en astro.config.mjs: quedaría congelada la del build. La salida limpia es mover la construcción del driver a su propio módulo —un entrypoint— que se ejecuta en runtime y lee las credenciales entonces.
// src/session-driver.ts — se evalua en runtime, no en el build
import type { SessionDriver } from 'astro';
import redisDriver from 'unstorage/drivers/redis';
import { REDIS_HOST, REDIS_PORT } from 'astro:env';
export default function (): SessionDriver {
return redisDriver({ host: REDIS_HOST, port: REDIS_PORT });
}
// astro.config.mjs
session: { driver: { entrypoint: new URL('./src/session-driver.ts', import.meta.url) } }
Con ese entrypoint, el módulo se importa y ejecuta en el servidor —no en el build—, de modo que astro:env le entrega los valores reales del entorno donde corre y la credencial deja de quedar congelada en el artefacto. Es la diferencia entre configurar qué driver usar en tiempo de compilación y resolver con qué datos conectarlo en tiempo de ejecución.
Un driver a medida en tres funciones
Bajo toda esta variedad hay una interfaz diminuta, y verla desnuda desmitifica el almacenamiento de una vez. Un SessionDriver de Astro es un objeto con tres métodos asíncronos —setItem, getItem y removeItem— y nada más. Si sabes guardar, leer y borrar un valor por clave en tu almacén favorito, ya sabes escribir un driver de sesión.
// src/mi-driver.ts
import type { SessionDriver } from 'astro';
import { LRUCache } from 'lru-cache';
export default function (config: { max?: number }): SessionDriver {
const cache = new LRUCache<string, any>({ max: config.max ?? 500 });
return {
setItem: async (clave, valor) => { cache.set(clave, valor); },
getItem: async (clave) => cache.get(clave),
removeItem: async (clave) => { cache.delete(clave); },
};
}
Rara vez tendrás que llegar tan lejos: los tipos de unstorage son compatibles con los de Astro, así que casi siempre reexportas un driver del catálogo y le pasas su configuración, sin escribir la lógica de almacenamiento. Pero conocer el contrato mínimo tiene valor: explica por qué hay tantos backends, por qué migrar entre ellos no roza tu código de páginas, y te deja implementar un almacén exótico —una tabla de tu propia base de datos, un servicio interno— el día que ninguno del catálogo encaje.
Que un driver quepa en tres funciones no es un accidente de diseño, es el reflejo de una verdad más honda: guardar, leer y borrar por clave es todo lo que un almacén clave-valor sabe hacer, y una sesión no necesita más. Al reducir el requisito a ese mínimo, Astro abre la puerta a que cualquier cosa capaz de esos tres verbos —desde un objeto en memoria hasta una nube global— sea un almacén de sesión válido. La lección para tu propio código: cuanto más pequeña sea la interfaz que exiges, más implementaciones podrás enchufar detrás de ella.
La lista de drivers parece un menú de conveniencia —elige tu base de datos favorita— pero enseña una lección de ingeniería que trasciende las sesiones. Fíjate en lo que no cambia cuando pasas de un archivo en disco a un Redis en la nube: tu código. Ni una página, ni un endpoint, ni una línea que llame a session.set se entera de la mudanza. Eso es posible porque tu código no habla con Redis ni con el sistema de archivos: habla con una interfaz —guardar, leer, borrar por clave— y es Astro, a través de unstorage, quien traduce esos tres verbos al dialecto concreto de cada backend. Este es el principio de inversión de dependencias en estado puro: los detalles de bajo nivel —qué base de datos, qué protocolo, qué credenciales— dependen de una abstracción estable, y no al revés. La recompensa es doble y profunda. Primero, la portabilidad real: empiezas un prototipo con el driver de memoria, sin infraestructura, y el día que necesitas escalar cambias una línea de configuración, no una arquitectura. Segundo, la testabilidad: como tu lógica depende de la interfaz y no del proveedor, puedes sustituir el almacén por uno falso en tus pruebas sin levantar un Redis. La tentación contraria —esparcir llamadas al cliente de tu base de datos por toda la aplicación— parece más directa el primer día y se convierte en una condena el día que quieres cambiar de proveedor, migrar al edge o simplemente escribir un test. La frontera nombrada que unstorage interpone entre tu intención —recordar un dato— y su realización —escribir en tal sitio— es lo que convierte una decisión de infraestructura en algo reversible. Y una decisión reversible es, casi siempre, una decisión que puedes tomar tarde, con más información, en lugar de temprano y a ciegas.
- Arranca en desarrollo con
sessionDrivers.lruCachey comprueba que las sesiones se pierden al reiniciar el servidor. - Cambia al driver de filesystem con
entrypoint: 'unstorage/drivers/fs'y verifica que ahora sobreviven a un reinicio: busca los archivos en la carpetabase. - Sin tocar ninguna página, razona qué línea cambiarías para pasar a Redis en un despliegue con dos instancias, y por qué el filesystem fallaría ahí.
- Explica por qué leer
process.envdentro deastro.config.mjspuede congelar una credencial y cómo lo evita un entrypoint en runtime.