Astro Actions y Middleware: Backend Moderno
El desarrollo moderno en Astro va mucho más allá de sitios estáticos: permite ejecutar lógica de backend segura mediante Astro Actions (mutaciones de datos con validación tipada) y Middleware (interceptación global de peticiones, autenticación y sesiones).
1. ¿Por qué usar Astro Actions en lugar de endpoints API manuales? #
Tradicionalmente, para procesar un formulario (como un registro o un contacto), debías:
- Crear una ruta API manual (
src/pages/api/contacto.js). - Escribir un
fetch('/api/contacto', { method: 'POST', body: ... })en el cliente. - Parsear JSON manualmente en el backend y escribir validaciones repetitivas con
try/catch.
Con Astro Actions:
- Defines funciones backend seguras con validación automática mediante Zod.
- Las invocas en el cliente como funciones JavaScript normales con autocompletado TypeScript al 100%.
- Manejo estandarizado de respuestas exitosas (
data) y errores de validación (error).
2. Definir una Acción en src/actions/index.ts #
Crea el archivo central de acciones:
📄 src/actions/index.ts
1import { defineAction, ActionError } from 'astro:actions'; 2import { z } from 'astro:schema'; 3 4export const server = { 5 // Acción para suscribirse a una newsletter 6 newsletter: defineAction({ 7 input: z.object({ 8 email: z.string().email('Introduce un correo electrónico válido'), 9 }), 10 handler: async (input, context) => { 11 // 1. input ya está validado y tipado automáticamente por Zod 12 console.log(`Guardando suscriptor: ${input.email}`); 13 14 // 2. Simulación de guardado en base de datos 15 if (input.email.endsWith('@spam.com')) { 16 throw new ActionError({ 17 code: 'BAD_REQUEST', 18 message: 'Dominio de correo no permitido', 19 }); 20 } 21 22 return { 23 success: true, 24 message: `¡Gracias por suscribirte con ${input.email}!`, 25 }; 26 }, 27 }), 28};
3. Llamar a una Acción desde componentes de cliente #
Puedes invocar la acción desde cualquier componente .astro o framework interactivo (React, Vue, Svelte) usando el módulo virtual astro:actions:
1--- 2// src/pages/newsletter.astro 3--- 4<form id="newsletter-form" class="max-w-md mx-auto p-6 bg-white shadow rounded-lg"> 5 <label class="block mb-2 font-semibold">Suscríbete al boletín:</label> 6 <input type="email" id="email" required class="border p-2 w-full rounded mb-4" /> 7 <button type="submit" class="bg-indigo-600 text-white px-4 py-2 rounded">Suscribirme</button> 8 <p id="mensaje" class="mt-4 hidden"></p> 9</form> 10 11<script> 12 import { actions } from 'astro:actions'; 13 14 const form = document.querySelector('#newsletter-form') as HTMLFormElement; 15 const mensaje = document.querySelector('#mensaje') as HTMLParagraphElement; 16 17 form?.addEventListener('submit', async (e) => { 18 e.preventDefault(); 19 const email = (document.querySelector('#email') as HTMLInputElement).value; 20 21 // Llamada tipada directa a la acción backend 22 const { data, error } = await actions.newsletter({ email }); 23 24 mensaje.classList.remove('hidden'); 25 if (error) { 26 mensaje.textContent = `Error: ${error.message}`; 27 mensaje.className = 'mt-4 text-red-600'; 28 } else { 29 mensaje.textContent = data.message; 30 mensaje.className = 'mt-4 text-green-600'; 31 form.reset(); 32 } 33 }); 34</script>
4. Acciones directas en formularios HTML nativos (sin JS) #
Astro Actions también soporta el envío tradicional de formularios HTML sin JavaScript mediante progressive enhancement:
1--- 2// src/pages/contacto.astro 3import { actions } from 'astro:actions'; 4 5// Lee el resultado de la acción ejecutada en el envío del formulario 6const result = Astro.getActionResult(actions.newsletter); 7--- 8 9{result && !result.error && ( 10 <div class="p-4 bg-green-100 text-green-800 rounded mb-4"> 11 {result.data.message} 12 </div> 13)} 14 15<form method="POST" action={actions.newsletter}> 16 <input type="email" name="email" required /> 17 <button type="submit">Enviar</button> 18</form>
5. ¿Qué es el Middleware en Astro? #
El Middleware es una función que se ejecuta antes de cada petición hacia cualquier página o endpoint de tu servidor.
Se define en el archivo 📄 src/middleware.ts:
1import { defineMiddleware } from 'astro:middleware'; 2 3export const onRequest = defineMiddleware(async (context, next) => { 4 const url = new URL(context.request.url); 5 6 // 1. Proteger rutas privadas (ej. /admin) 7 if (url.pathname.startsWith('/admin')) { 8 const token = context.cookies.get('auth_token')?.value; 9 10 if (!token) { 11 return context.redirect('/login'); 12 } 13 14 // 2. Inyectar datos del usuario autenticado en context.locals 15 context.locals.user = { id: 1, nombre: 'Admin', rol: 'superadmin' }; 16 } 17 18 // 3. Continuar hacia la página solicitada 19 const response = await next(); 20 21 // 4. Modificar cabeceras de seguridad en la respuesta 22 response.headers.set('X-Frame-Options', 'DENY'); 23 return response; 24});
6. Compartir datos con Astro.locals #
Los datos inyectados en context.locals dentro del middleware están disponibles directamente en el frontmatter de cualquier página .astro:
📄 src/pages/admin/dashboard.astro
1--- 2// Lee la información del usuario autenticado por el middleware 3const usuario = Astro.locals.user; 4--- 5 6<h1>Panel de Control</h1> 7<p>Bienvenido, {usuario.nombre} ({usuario.rol})</p>
Para tener autocompletado TypeScript en Astro.locals, declara sus tipos en src/env.d.ts:
1/// <reference path="../.astro/types.d.ts" /> 2 3declare namespace App { 4 interface Locals { 5 user?: { 6 id: number; 7 nombre: string; 8 rol: string; 9 }; 10 } 11}
Resumen del tema
Conceptos clave #
- Astro Actions (
src/actions/index.ts): arquitectura RPC segura que expone funciones de servidor con tipado estricto y esquemas de entrada Zod. - Manejo de errores tipado (
ActionError): retorno consistente de estados (datavserror) sin necesidad de excepciones manuales en el cliente. - Integración con formularios nativos: soporte para
action={actions.miAccion}y lectura conAstro.getActionResult(). - Middleware global (
src/middleware.ts): interceptor de ciclo de vida para control de acceso, verificación de cookies y cabeceras de respuesta. - Almacenamiento de contexto (
Astro.locals): contenedor de datos por petición para usuarios autenticados y variables de sesión.
Qué debes recordar #
Usa Astro Actions para formularios y mutaciones tipadas con Zod, y Middleware con Astro.locals para proteger rutas y autenticar sesiones de forma centralizada.