Hasta ahora, todo lo que hemos puesto dentro de la carpeta src/pages han sido componentes (.astro, .md) que renderizan HTML.
Pero, ¿y si no queremos devolver HTML?
Imagina que quieres:
- Crear una API REST interna para que tu frontend consuma datos.
- Generar un archivo
sitemap.xmlorobots.txtdinámicamente. - Recibir los datos de un formulario mediante una petición
POST. - Generar imágenes al vuelo.
Para todo esto, Astro nos permite crear Endpoints (también conocidos como API Routes).
¿Qué es un Endpoint en Astro?
Un Endpoint es un archivo dentro de src/pages que no exporta un componente Astro, sino que exporta funciones correspondientes a los métodos HTTP (GET, POST, PUT, DELETE, etc.).
Estos archivos deben tener la extensión .js o .ts (no .astro).
Nuestro primer Endpoint (GET)
Vamos a crear una ruta que devuelva un saludo en formato JSON. Creamos el archivo src/pages/api/saludo.ts.
// src/pages/api/saludo.ts
import type { APIRoute } from 'astro';
export const GET: APIRoute = async ({ params, request }) => {
return new Response(
JSON.stringify({
mensaje: "¡Hola desde la API de Astro!",
hora: new Date().toISOString()
}),
{
status: 200,
headers: {
"Content-Type": "application/json"
}
}
);
}
Si ahora visitas http://localhost:4321/api/saludo, verás el JSON en tu navegador.
Estándares Web: Astro utiliza los objetos estándar Request y Response de la API Fetch. Si has trabajado con Service Workers o Cloudflare Workers, esto te resultará familiar. No hay objetos req y res propietarios como en Express.
Contexto del Endpoint
La función que exportamos recibe un objeto de contexto (similar al objeto Astro global en los componentes .astro). De aquí podemos extraer:
params: Los parámetros dinámicos de la ruta (ej:[id]).request: El objeto Request estándar (contiene headers, body, url…).cookies: Utilidades para leer y escribir cookies.redirect: Función para redirigir al usuario.
Endpoints dinámicos
Al igual que con las páginas, podemos usar corchetes para crear rutas de API dinámicas.
Imagina que queremos una API para obtener datos de un producto: src/pages/api/productos/[id].ts.
// src/pages/api/productos/[id].ts
import type { APIRoute } from 'astro';
import { getProductById } from '../../lib/db'; // Imaginemos una BBDD
export const GET: APIRoute = async ({ params, request }) => {
const id = params.id;
// Buscamos en nuestra "base de datos"
const producto = await getProductById(id);
if (!producto) {
return new Response(JSON.stringify({ error: "No encontrado" }), {
status: 404,
});
}
return new Response(JSON.stringify(producto), {
status: 200,
headers: { "Content-Type": "application/json" }
});
}
Nota importante sobre SSG vs SSR
Aquí ocurre lo mismo que vimos en el capítulo anterior:
- Modo
output: 'static'(SSG): Necesitas exportar la funcióngetStaticPathsdentro del archivo.tspara decirle a Astro qué endpoints debe generar al compilar. El resultado serán archivos estáticos (producto-1.json,producto-2.json). - Modo
output: 'server'(SSR): No necesitasgetStaticPaths. El endpoint se ejecutará en tiempo real cuando alguien haga la petición.
Manejando peticiones POST (Formularios y Datos)
Los Endpoints son el lugar perfecto para procesar formularios o recibir datos desde el cliente. Vamos a ver cómo manejar un POST para recibir datos de contacto.
// src/pages/api/contacto.ts
import type { APIRoute } from 'astro';
export const POST: APIRoute = async ({ request }) => {
try {
// Leemos el cuerpo de la petición.
// Si envían JSON usamos request.json()
// Si es un Formulario HTML usamos request.formData()
const body = await request.json();
const { nombre, email } = body;
// Validación básica
if (!nombre || !email) {
return new Response(
JSON.stringify({ message: "Faltan datos" }),
{ status: 400 }
);
}
// Aquí llamaríamos a nuestro servicio de email o base de datos
console.log(`Guardando contacto: ${nombre}`);
return new Response(
JSON.stringify({ message: "¡Recibido con éxito!" }),
{ status: 200 }
);
} catch (error) {
return new Response(
JSON.stringify({ message: "Error del servidor" }),
{ status: 500 }
);
}
}
Puedes exportar GET, POST, PUT, DELETE y ALL en el mismo archivo para manejar diferentes métodos sobre la misma ruta.
Generando otros formatos (Imágenes, XML)
No estamos limitados a JSON. Podemos devolver lo que queramos.
Un caso de uso muy común es generar un robots.txt dinámicamente según si estamos en producción o desarrollo.
// src/pages/robots.txt.ts
import type { APIRoute } from 'astro';
const getRobotsTxt = (baseUrl: URL) => `
User-agent: *
Allow: /
Sitemap: ${baseUrl.href}sitemap-index.xml
`.trim();
export const GET: APIRoute = ({ site }) => {
return new Response(getRobotsTxt(site), {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
},
});
};
