Acceder al request: getRequestEvent, headers y cookies
Dentro de una función de servidor no hay props ni contexto reactivo que te digan quién pide: hay una petición HTTP, y se accede a ella con `getRequestEvent` de `solid-js/web`. Se estudia la anatomía del `RequestEvent` —el `request` entrante con sus headers y cookies, el `response` saliente donde fijas estado y `Set-Cookie`, el `locals` tipado que rellena el middleware, y el `nativeEvent` de Vinxi para los helpers de bajo nivel—, por qué en el cliente devuelve `undefined`, y por qué la petición es la única fuente de verdad por-petición en un proceso que sirve a muchos.
Dentro de una función de servidor desaparece todo el andamiaje del cliente: no hay props que te digan quién llama, no hay contexto reactivo, no hay window. Lo que sí hay —lo único que hay— es una petición HTTP en curso, y con ella toda la verdad sobre quién pide, con qué cabeceras y qué cookies. El puente hacia esa petición es getRequestEvent, y dominar su anatomía es lo que separa una función de servidor que solo calcula de una que autentica, lee cookies, negocia contenido y responde con las cabeceras correctas. Esta lección abre en canal el RequestEvent.
- Obtener la petición en curso con
getRequestEventdesolid-js/weby entender por qué en el cliente devuelveundefined. - Leer del
requestentrante: cabeceras, método, URL y cookies. - Escribir en el
responsesaliente: código de estado y cabeceras comoSet-Cookie. - Distinguir
locals—contexto tipado por petición— denativeEvent—el acceso de bajo nivel de Vinxi—.
getRequestEvent: el puente a la petición
getRequestEvent, importado de solid-js/web, devuelve el evento de la petición que se está atendiendo ahora mismo en el servidor. No recibe argumentos: sabe cuál es la petición actual gracias al almacenamiento asíncrono local que el runtime mantiene por petición. Como solo existe una petición en el servidor, en el cliente devuelve undefined —allí no hay ninguna petición que atender—, lo que lo convierte también en una señal de entorno: si hay evento, estás en el servidor.
import { getRequestEvent } from "solid-js/web";
export async function idioma() {
"use server";
const evento = getRequestEvent();
const cabecera = evento?.request.headers.get("accept-language") ?? "es";
return cabecera.split(",")[0]; // negociacion simple de idioma
}
flowchart TD P[peticion HTTP entrante] --> E[RequestEvent] E --> RQ[request: headers, metodo, url, cookies] E --> RS[response: estado y cabeceras salientes] E --> L[locals: contexto tipado por peticion] E --> NV[nativeEvent: acceso de bajo nivel de Vinxi] style E fill:#89b4fa,color:#11111b style L fill:#a6e3a1,color:#11111b style RS fill:#f9e2af,color:#11111b
Leer la entrada y escribir la salida
El RequestEvent tiene dos caras. La entrante es event.request, un objeto Request estándar de la plataforma: de él lees headers, method, url y, a través de la cabecera cookie, las cookies del cliente. La saliente es event.response, donde depositas lo que la respuesta debe llevar: un status distinto y cabeceras como Set-Cookie. Es importante ver la simetría —una cara es lo que llega, la otra lo que se irá— porque muchas operaciones de sesión consisten precisamente en leer una cookie de request y escribir otra en response.
import { getRequestEvent } from "solid-js/web";
export async function marcarVisita() {
"use server";
const evento = getRequestEvent();
if (!evento) return;
const cookies = evento.request.headers.get("cookie") ?? "";
const yaVino = cookies.includes("visitante=");
if (!yaVino) {
evento.response.headers.append("set-cookie", "visitante=1; Path=/; HttpOnly");
evento.response.status = 201; // primera visita
}
return { recurrente: yaVino };
}
locals y nativeEvent: contexto y bajo nivel
Las dos piezas restantes resuelven necesidades distintas. event.locals es un contenedor por petición para datos que un middleware calcula una vez y muchas funciones consumen: el usuario autenticado, un identificador de traza, un nonce de CSP. Es el canal correcto para pasar contexto sin variables de módulo —que, como viste en el nivel de estado, se comparten entre peticiones en el servidor—. Y se tipa ampliando una interfaz global, de modo que leer locals sea tan seguro como leer cualquier objeto tipado.
// app.d.ts — tipar los locals una sola vez
declare module "@solidjs/start/env" {
interface RequestEventLocals {
usuario?: { id: string; rol: "admin" | "editor" };
}
}
export async function panelAdmin() {
"use server";
const usuario = getRequestEvent()?.locals.usuario; // tipado, puesto por el middleware
// ...decidir en funcion del usuario
}
Cuando necesitas algo que la cara de alto nivel no ofrece, event.nativeEvent te da el evento subyacente de Vinxi, sobre el que operan los helpers del ecosistema —lectura y escritura de cookies con parseo, por ejemplo—. La advertencia de la lección anterior vuelve con fuerza: esos helpers no hacen treeshaking, así que solo se importan en archivos exclusivamente de servidor.
Cookies con parseo: los helpers de Vinxi
Manipular cookies a mano —concatenar la cabecera Set-Cookie, parsear la cadena cookie con split— funciona pero es frágil: te toca gestionar el escapado, Max-Age, HttpOnly, SameSite y el resto de atributos sin red de seguridad. Los helpers getCookie y setCookie de vinxi/http operan sobre el nativeEvent y hacen ese trabajo por ti, leyendo y escribiendo cookies con parseo y opciones tipadas. El precio, ya conocido, es que no hacen treeshaking: viven en archivos marcados con la directiva de archivo.
// data/tema.ts — solo de servidor
"use server";
import { getRequestEvent } from "solid-js/web";
import { getCookie, setCookie } from "vinxi/http";
export async function leerTema() {
const evento = getRequestEvent()!; // en este archivo siempre hay evento
return getCookie(evento.nativeEvent, "tema") ?? "claro";
}
export async function fijarTema(valor: "claro" | "oscuro") {
const evento = getRequestEvent()!;
setCookie(evento.nativeEvent, "tema", valor, {
httpOnly: true,
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24 * 365,
});
}
El patrón de sesión completo se apoya en esta base y en useSession, que cifra y firma una cookie de sesión sobre el mismo nativeEvent; lo verás en detalle al tratar autenticación, pero su cimiento es exactamente el que acabas de ver: toda la identidad de la petición se lee y se escribe a través de su evento, nunca de un estado global del proceso.
request
La cara entrante: headers, method, url y las cookies del cliente, como Request estándar.
response
La cara saliente: fijas status y cabeceras como Set-Cookie antes de que la respuesta parta.
locals
Contexto tipado por petición que rellena el middleware; el canal correcto para el usuario o el nonce.
Como el evento solo existe en el servidor, getRequestEvent devuelve undefined en el cliente y en cualquier código isomorfo que se ejecute allí. Llamarlo fuera de un cuerpo de servidor y acceder a sus campos sin comprobar es un cannot read properties of undefined esperando a suceder. La disciplina es doble: invócalo solo dentro de funciones marcadas con "use server", y aun así trata su retorno como opcional con ?.. Esa opcionalidad no es paranoia del tipado, es el reflejo honesto de que el mismo módulo puede evaluarse en dos mundos y solo uno tiene petición.
Merece la pena conectar esta lección con la lección de estado y SSR, porque juntas cierran una idea central del servidor. Un proceso SSR atiende a muchos usuarios a la vez con las mismas variables de módulo, así que ninguna variable global puede contarte con seguridad quién está pidiendo: cualquier dato que guardes «para el usuario actual» en el ámbito de módulo se contamina con el de otro en cuanto dos peticiones se solapan. La única cosa que en el servidor es genuinamente de esta petición y de ninguna otra es el propio evento de la petición: su request, sus cabeceras, sus cookies, su locals. Por eso getRequestEvent no es una utilidad más, es el ancla de identidad del servidor: es el mecanismo por el que un cuerpo de código que se evalúa una vez y sirve a multitudes recupera, en cada invocación, el hilo concreto que lo llamó. Cuando necesites saber quién pide, qué idioma prefiere, qué sesión trae o qué permiso tiene, la respuesta nunca vive en una variable del módulo: vive en el evento, y llega a través de él o del locals que un middleware derivó de él. Interioriza esto y adquieres el reflejo que evita la clase entera de fugas entre peticiones: en el servidor, todo lo que dependa de quién pregunta se lee de la petición, jamás de un global. La petición es el presente; el módulo es el siempre. No los confundas.
- Escribe una función de servidor que lea
accept-languagede las cabeceras y devuelva el idioma preferido, con un valor por defecto si falta. - Lee una cookie de
event.requesty, si no existe, fíjala enevent.responseconSet-Cookie; comprueba el ciclo en dos peticiones seguidas. - Cambia
event.response.statusen función de una condición y confirma el código en la pestaña de red. - Tipa
RequestEventLocalscon un campousuarioy léelo desde una función de servidor asumiendo que un middleware lo rellenó. - Llama a
getRequestEventdesde código que corre en el cliente, observa que esundefined, y razona por qué su tipo de retorno es opcional.