Saltar al contenido

Middleware

Middleware permite interceptar solicitudes y respuestas e inyectar comportamientos dinámicamente cada vez que una página o endpoint está a punto de ser renderizado. Este renderizado ocurre en tiempo de build para todas las páginas pre-renderizadas, pero ocurre cuando la ruta es solicitada para las páginas renderizadas bajo demanda, haciendo que características SSR adicionales como cookies y headers estén disponibles.

Middleware también permite establecer y compartir información específica de la solicitud entre endpoints y páginas mutando un objeto locals que está disponible en todos los componentes de Astro y endpoints de API. Este objeto está disponible incluso cuando el middleware se ejecuta en tiempo de build.

  1. Crea src/middleware.js|ts (Alternativamente, puedes crear src/middleware/index.js|ts.)

  2. Dentro de este archivo, exporta una función onRequest() que puede recibir un objeto context y una función next(). Esta no debe ser una exportación por defecto.

    src/middleware.js
    export function onRequest (context, next) {
    // intercept data from a request
    // optionally, modify the properties in `locals`
    context.locals.title = "New title";
    context.locals.property = "information";
    // return a Response or the result of calling `next()`
    return next();
    };
  3. Dentro de cualquier archivo .astro, accede a los datos de respuesta usando Astro.locals.

    src/components/Component.astro
    ---
    const data = Astro.locals;
    ---
    <h1>{data.title}</h1>
    <p>This {data.property} is from middleware.</p>

El objeto context incluye información para hacer disponible a otros middleware, rutas de API y rutas .astro durante el proceso de renderizado.

Este es un argumento opcional pasado a onRequest() que puede contener el objeto locals así como cualquier propiedad adicional para ser compartida durante el renderizado. Por ejemplo, el objeto context puede incluir cookies usadas en la autenticación.

context.locals es un objeto que puede ser manipulado dentro del middleware.

Este objeto locals se reenvía a través del proceso de manejo de solicitudes y está disponible como propiedad de APIContext y AstroGlobal. Esto permite que los datos se compartan entre middlewares, rutas de API, y páginas .astro. Esto es útil para almacenar datos específicos de la solicitud, como datos de usuario, a través del paso de renderizado.

Puedes almacenar cualquier tipo de dato dentro de locals: strings, números, e incluso tipos de datos complejos como funciones y mapas.

src/middleware.js
export function onRequest (context, next) {
// intercept data from a request
// optionally, modify the properties in `locals`
context.locals.user = { id: 1, name: "John Wick" };
context.locals.welcomeTitle = () => {
return "Welcome back " + context.locals.user.name;
};
context.locals.orders = new Map([["1", { product: "socks" }]]);
context.locals.property = "information";
// return a Response or the result of calling `next()`
return next();
};

Luego puedes usar esta información dentro de cualquier archivo .astro con Astro.locals.

src/pages/orders.astro
---
const title = Astro.locals.welcomeTitle();
const orders = Array.from(Astro.locals.orders.entries());
const data = Astro.locals;
---
<h1>{title}</h1>
<p>This {data.property} is from middleware.</p>
<ul>
{orders.map(order => {
return <li>{/* do something with each order */}</li>;
})}
</ul>

locals es un objeto que vive y muere dentro de una sola ruta de Astro; cuando la página de tu ruta es renderizada, locals ya no existirá y se creará uno nuevo. La información que necesita persistir a través de múltiples solicitudes de página debe almacenarse en otro lugar.

El ejemplo siguiente usa middleware para reemplazar “PRIVATE INFO” con la palabra “REDACTED” para permitirte renderizar HTML modificado en tu página:

src/middleware.js
export const onRequest = async (context, next) => {
const response = await next();
const html = await response.text();
const redactedHtml = html.replaceAll("PRIVATE INFO", "REDACTED");
return new Response(redactedHtml, {
status: 200,
headers: response.headers
});
};

Puedes importar y usar la función utilitaria defineMiddleware() para aprovechar la seguridad de tipos:

src/middleware.ts
import { defineMiddleware } from "astro:middleware";
// `context` and `next` are automatically typed
export const onRequest = defineMiddleware((context, next) => {
});

En su lugar, si estás usando JsDoc para aprovechar la seguridad de tipos, puedes usar MiddlewareHandler:

src/middleware.js
/**
* @type {import("astro").MiddlewareHandler}
*/
// `context` and `next` are automatically typed
export const onRequest = (context, next) => {
};

Para tipar la información dentro de Astro.locals, lo que te da autocompletado dentro de los archivos .astro y el código del middleware, extiende los tipos globales declarando un namespace global en el archivo env.d.ts:

src/env.d.ts
type User = {
id: number;
name: string;
};
declare namespace App {
interface Locals {
user: User;
welcomeTitle: () => string;
orders: Map<string, object>;
session: import("./lib/server/session").Session | null;
}
}

Luego, dentro del archivo de middleware, puedes aprovechar el autocompletado y la seguridad de tipos.

Múltiples middlewares se pueden unir en un orden especificado usando sequence():

src/middleware.js
import { sequence } from "astro:middleware";
async function validation(_, next) {
console.log("validation request");
const response = await next();
console.log("validation response");
return response;
}
async function auth(_, next) {
console.log("auth request");
const response = await next();
console.log("auth response");
return response;
}
async function greeting(_, next) {
console.log("greeting request");
const response = await next();
console.log("greeting response");
return response;
}
export const onRequest = sequence(validation, auth, greeting);

Esto resultará en el siguiente orden en la consola:

Ventana de la terminal
validation request
auth request
greeting request
greeting response
auth response
validation response

Agregado en: astro@4.13.0

El APIContext expone un método llamado rewrite() que funciona de la misma manera que Astro.rewrite.

Usa context.rewrite() dentro del middleware para mostrar el contenido de una página diferente sin redirigir a tu visitante a una nueva página. Esto disparará una nueva fase de renderizado, causando que cualquier middleware se vuelva a ejecutar.

src/middleware.js
import { isLoggedIn } from "~/auth.js"
export function onRequest (context, next) {
if (!isLoggedIn(context)) {
// If the user is not logged in, update the Request to render the `/login` route and
// add header to indicate where the user should be sent after a successful login.
// Re-execute middleware.
return context.rewrite(new Request("/login", {
headers: {
"x-redirect-to": context.url.pathname
}
}));
}
return next();
};

También puedes pasar a la función next() un parámetro opcional de ruta URL para reescribir el Request actual sin redisparar una nueva fase de renderizado. La ubicación de la ruta de reescritura se puede proporcionar como un string, URL, o Request:

src/middleware.js
import { isLoggedIn } from "~/auth.js"
export function onRequest (context, next) {
if (!isLoggedIn(context)) {
// If the user is not logged in, update the Request to render the `/login` route and
// add header to indicate where the user should be sent after a successful login.
// Return a new `context` to any following middlewares.
return next(new Request("/login", {
headers: {
"x-redirect-to": context.url.pathname
}
}));
}
return next();
};

La función next() acepta la misma carga útil de la función Astro.rewrite(). La ubicación de la ruta de reescritura se puede proporcionar como un string, URL, o Request.

Cuando tienes múltiples funciones de middleware encadenadas vía sequence(), pasar una ruta a next() reescribirá el Request en su lugar y el middleware no se ejecutará nuevamente. La siguiente función de middleware en la cadena recibirá el nuevo Request con su context actualizado.

Llamar a next() con esta firma creará un nuevo objeto Request usando el antiguo ctx.request. Esto significa que intentar consumir Request.body, ya sea antes o después de esta reescritura, lanzará un error en tiempo de ejecución. Este error a menudo se produce con Astro Actions que usan formularios HTML. En estos casos, recomendamos manejar las reescrituras desde tus plantillas de Astro usando Astro.rewrite() en lugar de usar middleware.

src/middleware.js
// Current URL is https://example.com/blog
// First middleware function
async function first(context, next) {
console.log(context.url.pathname) // this will log "/blog"
// Rewrite to a new route, the homepage
// Return updated `context` which is passed to next function
return next("/")
}
// Current URL is still https://example.com/blog
// Second middleware function
async function second(context, next) {
// Receives updated `context`
console.log(context.url.pathname) // this will log "/"
return next()
}
export const onRequest = sequence(first, second);

El middleware intentará ejecutarse para todas las páginas renderizadas bajo demanda, incluso cuando no se pueda encontrar una ruta coincidente. Esto incluye la página 404 por defecto (en blanco) de Astro y cualquier página 404 personalizada. Sin embargo, depende del adaptador decidir si ese código se ejecuta. Algunos adaptadores pueden servir una página de error específica de la plataforma en su lugar.

El middleware también intentará ejecutarse antes de servir una página de error 500, incluyendo una página 500 personalizada, a menos que el error del servidor haya ocurrido en la ejecución del middleware mismo. Si tu middleware no se ejecuta exitosamente, entonces no tendrás acceso a Astro.locals para renderizar tu página 500.

Contribuir Comunidad Patrocinar