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.
// 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}`); },});// 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.
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.
// src/global.d.tsimport "@solidjs/start/server";declare module "@solidjs/start/server" { interface RequestEventLocals { inicio: number; user: { id: string; email: string } | null; }}// 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.
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.