Saltar al contenido

Middleware: createMiddleware, onRequest y locals tipados

El middleware intercepta cada petición y respuesta del servidor para tareas transversales: cabeceras, redirecciones, telemetría y, sobre todo, poblar `event.locals` con datos de la petición. Se declara con `createMiddleware` de `@solidjs/start/middleware`, se registra en `app.config.ts`, y corre en dos momentos: `onRequest` antes del handler y `onBeforeResponse` después. Este capítulo tipa `event.locals` por fusión de declaraciones y explica el límite crítico que decide toda tu arquitectura de auth: el middleware no corre en las navegaciones de cliente, así que no es una frontera de seguridad.

createMiddleware: interceptar la petición

El middleware es un objeto de configuración que exportas por defecto desde un archivo dedicado. createMiddleware le da forma y tipos; app.config.ts le dice a SolidStart dónde encontrarlo. Sin ese registro, el archivo es inerte.

TypeScript
// src/middleware/index.tsimport { createMiddleware } from "@solidjs/start/middleware";export default createMiddleware({  onRequest: (event) => {    event.locals.inicio = Date.now();            // guarda para medir despues  },  onBeforeResponse: (event) => {    const ms = Date.now() - event.locals.inicio; // lee lo que dejo onRequest    event.response.headers.set("Server-Timing", `total;dur=${ms}`);  },});
TypeScript
// app.config.tsimport { defineConfig } from "@solidjs/start/config";export default defineConfig({  middleware: "src/middleware/index.ts",});

El event que reciben los handlers es un FetchEvent: expone event.request (la Request estándar), event.response (con sus headers mutables), event.locals (el maletín por petición) y event.nativeEvent (el evento h3 subyacente, que necesitan los helpers de cookies de vinxi).

onRequest y onBeforeResponse: dos momentos del ciclo

Los dos ganchos ocupan lados opuestos del handler. onRequest corre antes: es el sitio para poblar event.locals, ajustar cabeceras de entrada o cortar la petición con una redirección temprana. onBeforeResponse corre después de que el handler produjo su resultado pero antes de enviarlo: el sitio para fijar cabeceras de salida o registrar métricas de la respuesta.

Devolver un valor desde onRequest cortocircuita todo el pipeline: ni corren más middlewares ni se ejecuta el handler; ese valor se convierte en la respuesta. Solo se admiten objetos Response, y Solid Router te da atajos para los casos comunes con redirect y json.

TypeScript
import { createMiddleware } from "@solidjs/start/middleware";import { redirect } from "@solidjs/router";export default createMiddleware({  onRequest: (event) => {    const { pathname } = new URL(event.request.url);    if (pathname === "/viejo") return redirect("/nuevo", 301); // corta aqui  },});

event.locals: el maletín tipado de la petición

event.locals es un objeto plano, vivo solo durante una petición y borrado al terminar, pensado para compartir datos entre el middleware y cualquier código de servidor: el estado de autenticación, un identificador de traza, metadatos del cliente. Dentro del middleware lo lees y escribes directo; en cualquier otro contexto de servidor lo alcanzas con getRequestEvent de solid-js/web.

Como es un objeto libre, TypeScript no conoce sus campos hasta que se los declaras. La forma canónica es la fusión de declaraciones sobre la interfaz RequestEventLocals del módulo @solidjs/start/server: la amplías una vez y todo event.locals de tu app queda tipado.

TypeScript
// src/global.d.tsimport "@solidjs/start/server";declare module "@solidjs/start/server" {  interface RequestEventLocals {    inicio: number;    user: { id: string; email: string } | null;  }}
TypeScript
// Leer locals desde una funcion de servidor cualquieraimport { getRequestEvent } from "solid-js/web";import { query } from "@solidjs/router";export const getUser = query(async () => {  "use server";  const event = getRequestEvent();  return event?.locals.user ?? null;   // lo que puso el middleware}, "user");

Cuando la lógica transversal crece, no la amontones en una función gigante: onRequest y onBeforeResponse aceptan también arrays de funciones que corren en orden, cada una tipada como FetchEvent. Así compones middlewares pequeños y enfocados, colocando cada uno después de aquellos de los que depende.

TypeScript
import { createMiddleware } from "@solidjs/start/middleware";import type { FetchEvent } from "@solidjs/start/server";function sello(event: FetchEvent) {  event.locals.inicio = Date.now();}function contexto(event: FetchEvent) {  // depende de nada previo; poblara event.locals.user en el nivel de auth}export default createMiddleware({  onRequest: [sello, contexto], // corren en este orden});

El límite: no es una frontera de seguridad

Aquí está la advertencia que lo cambia todo. El middleware no corre en cada petición: en particular, no corre durante las navegaciones de cliente, donde el router puede reutilizar datos ya cargados sin volver a tocar el servidor. Confiar en él para autorizar —«el middleware comprobó la sesión, luego el usuario está autenticado»— abre un agujero de seguridad, porque hay caminos que llegan a tus datos sin haber pasado por él.