wandres.dev
SERVER FUNCTIONS · "use server"

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.

⏱ 16 min

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.

🎯 Al terminar esta lección sabrás
  • Obtener la petición en curso con getRequestEvent de solid-js/web y entender por qué en el cliente devuelve undefined.
  • Leer del request entrante: cabeceras, método, URL y cookies.
  • Escribir en el response saliente: código de estado y cabeceras como Set-Cookie.
  • Distinguir locals —contexto tipado por petición— de nativeEvent —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.

⚠️
En el cliente getRequestEvent es undefined: no lo des por hecho

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.

La petición es la única verdad por-petición en un proceso compartido

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.

⚔️ Diseca el RequestEvent
  1. Escribe una función de servidor que lea accept-language de las cabeceras y devuelva el idioma preferido, con un valor por defecto si falta.
  2. Lee una cookie de event.request y, si no existe, fíjala en event.response con Set-Cookie; comprueba el ciclo en dos peticiones seguidas.
  3. Cambia event.response.status en función de una condición y confirma el código en la pestaña de red.
  4. Tipa RequestEventLocals con un campo usuario y léelo desde una función de servidor asumiendo que un middleware lo rellenó.
  5. Llama a getRequestEvent desde código que corre en el cliente, observa que es undefined, y razona por qué su tipo de retorno es opcional.