La evolución de la arquitectura web: la nostalgia de PHP y la pesadilla de CORS

«En aquella época, con un solo archivo bastaba…»

El PHP con el que trabajé en la universidad era realmente cómodo. En un solo archivo index.php podías meter las etiquetas HTML, abrir <?php ?> entre ellas y escribir también el código de consulta a la base de datos. No había una separación clara entre frontend y backend. Bastaba con declarar una variable para poder usarla en cualquier parte del HTML, y prácticamente nunca aparecían errores por la comunicación de datos.

Pero en cuanto en el trabajo cambié a una pila tecnológica con Vue.js en el frontend y Spring Boot en el backend, empezó el infierno.

«He levantado el servidor de Spring en local, la pantalla de Vue también está funcionando, ¿entonces por qué no llegan los datos?»

La consola de las herramientas de desarrollo de Chrome estaba completamente cubierta de mensajes de error en rojo intenso.

Access to XMLHttpRequest at ... has been blocked by CORS policy

¿Qué demonios es ese «CORS» que bloquea mis datos? ¿Y por qué dejamos atrás aquel método tan cómodo en el que con un solo archivo bastaba, para esforzarnos en separar a propósito el frontend y el backend?

Antes vivían bajo el mismo techo; ahora viven en casas distintas y tienen que pasar por un control de seguridad.

¿Quién cocina? (SSR vs CSR)

Para entender este cambio, primero hay que entender quién prepara el «plato» llamado página web, es decir, el HTML. Esa es precisamente la diferencia entre SSR (Server Side Rendering) y CSR (Client Side Rendering).

1. SSR: un almuerzo ya preparado (PHP, JSP)

  • Cómo funciona: el servidor (la cocina) prepara el arroz, las guarniciones y la sopa, lo coloca todo bien presentado en una fiambrera (HTML) y lo envía.
  • Cliente (navegador): solo tiene que abrir la tapa de la fiambrera y mostrar lo que hay dentro.
  • Ventajas: la primera pantalla carga rápido. A los buscadores (SEO) les resulta fácil leer el contenido.
  • Desventajas: aunque solo quieras cambiar una guarnición, tienes que recibir de nuevo la fiambrera entera. (La pantalla parpadea.)

2. CSR: un kit de comida a domicilio (React, Vue)

  • Cómo funciona: el servidor no cocina. Solo envía los ingredientes (datos JSON) y la receta (JavaScript).
  • Cliente (navegador): con los ingredientes recibidos, se pone a cocinar él mismo en mi propio ordenador (el navegador) y monta la pantalla.
  • Ventajas: como se pueden cambiar solo las guarniciones, una a una, las transiciones de pantalla son muy fluidas (parece una app). Se reduce la carga del servidor.
  • Desventajas: al principio hay que descargar la receta (los archivos JS), así que la carga inicial es lenta.

[Tip] CSR y SPA no son exactamente lo mismo

A menudo se usan como si fueran sinónimos, pero en sentido estricto no lo son.

  • CSR (Client Side Rendering): es el ‘cómo’ (How). Se refiere a la forma en que el navegador dibuja la pantalla.
  • SPA (Single Page Application): es el ‘qué’ (What). Se refiere a una aplicación que tiene una sola página.

Normalmente usamos CSR para construir una SPA. Es decir, como queremos evitar el parpadeo de recargar páginas usando una sola página (SPA), elegimos un enfoque en el que el propio navegador redibuja las partes necesarias (CSR).

La razón por la que usamos Vue.js o React es la experiencia de usuario. Para que un sitio web funcione con la suavidad de una aplicación móvil, trasladamos el acto de «cocinar» del servidor al navegador.

CORS, el guardia de seguridad que detiene a los intrusos

Al separar el frontend (Vue) y el backend (Spring), terminamos teniendo «dos casas».

  • Casa del frontend: localhost:8080 (servidor de Vue)
  • Casa del backend: localhost:3000 (servidor de Spring)

Y ahí empieza el problema. Por razones de seguridad, el navegador web tiene una regla básica: «no confíes a la ligera en recursos que vienen de otro Origin». Eso es la SOP, la Same Origin Policy.

Piénsalo así. Yo estoy conectado en Naver, y desde un sitio creado por un hacker se envía en secreto una petición al servidor de Naver diciendo: «dame la información personal de este usuario». Si el navegador no bloqueara eso, todos mis datos quedarían expuestos.

Por eso el navegador bloquea por defecto cualquier petición cuando el origen, es decir, el dominio o el puerto, es distinto. Pero nosotros, para desarrollar, dividimos deliberadamente los puertos. Solo queremos usar nuestros propios datos, y el navegador, como si fuera un guardia, nos dice: «Tu puerto no coincide. Prohibido el paso». Esa es la verdadera naturaleza del error CORS (Cross-Origin Resource Sharing).

[Code Verification] Emitir un pase de acceso

La forma de resolverlo es simple. El servidor, es decir, el backend, solo tiene que escribirle un pase al navegador, el guardia, diciéndole: «esta persona es un invitado mío, déjala entrar».

En Spring Boot, ese pase puede emitirse con un solo archivo de configuración.

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // 1. Para todas las rutas de la API
                .allowedOrigins("http://localhost:8080") // 2. Permitir solicitudes desde esta direccion
                .allowedMethods("GET", "POST", "PUT", "DELETE") // 3. Permitir este tipo de solicitudes
                .allowCredentials(true); // 4. Se permiten cookies y credenciales
    }
}

Análisis:

  1. allowedOrigins: es lo más importante. Si aquí escribes la dirección del servidor frontend (localhost:8080), el servidor de Spring estampa en la cabecera de la respuesta un sello que dice Access-Control-Allow-Origin: http://localhost:8080 antes de enviarla.
  2. El navegador comprueba ese sello y, pensando «ah, el dueño del servidor lo ha permitido», entrega los datos al frontend.

Lo importante es no caer en la pereza y abrir todo usando allowedOrigins("*"). Eso sería como dejar la puerta de casa completamente abierta con un cartel que diga: «Bienvenidos, ladrones».

Consejo práctico: ¿cuándo usar qué?

Entonces, ¿significa eso que CSR, es decir, Vue o React, es siempre la respuesta correcta? No. En la práctica, hay que elegir según el objetivo.

  1. Paneles de administración (Admin) y dashboards:
    • Recomendación: CSR (Vue, React)
    • Motivo: hay mucha interacción con los datos y no hace falta aparecer en los buscadores. Lo importante es una respuesta ágil, como la de una app.
  2. Sitios de noticias, blogs, portada de tiendas online:
    • Recomendación: SSR (Next.js, Nuxt.js)
    • Motivo: tienen que aparecer bien en las búsquedas de Google (SEO), y cuando entra un cliente el contenido debe mostrarse al instante en lugar de una pantalla en blanco.
  3. Proyectos personales pequeños:
    • Recomendación: Thymeleaf (SSR integrado en Spring Boot)
    • Motivo: si desarrollas tú solo, no merece la pena complicarte separando frontend y backend y peleándote con la configuración de CORS. Desarrollarlo todo junto, como en la época de PHP, es lo más productivo.

Cierre: cruzar la frontera

Aquella «gran familia bajo un mismo techo» de la época de PHP era cómoda, pero cuanto más crecía la complejidad, más difícil se volvía de mantener. El modelo actual, dos familias separadas, frontend y backend, es más molesto por cosas como CORS, pero permite que cada lado se concentre en su propio papel.

Así que la próxima vez que te encuentres con un error de CORS, intenta no reaccionar solo con enfado. El navegador no te está fastidiando: simplemente está haciendo una inspección estricta para proteger tu casa, es decir, tu servidor.

Ahora el camino entre frontend y backend ya está abierto. Los datos pueden ir y venir. Pero ¿dónde se almacenan realmente esos datos dentro del servidor? Antes bastaba con lanzar una consulta y traerlos. En Spring, en cambio, aparecen cosas como JPA y Entity, y da la sensación de que ya no te dejan escribir SQL directamente.

La próxima vez veremos cómo la «forma canónica» del diseño de bases de datos, la normalización, puede convertirse en un obstáculo en la práctica, y por qué JPA insiste tanto en ocultarte el SQL.

Deja un comentario