CORS en Express
CORS (Cross-Origin Resource Sharing) es un mecanismo del navegador, no del servidor — Express no “bloquea” nada por sí solo; es el navegador el que rechaza la respuesta si el servidor no autoriza explícitamente el origen que hizo la request.
Por qué existe: Same-Origin Policy
Sección titulada «Por qué existe: Same-Origin Policy»Por defecto, el navegador impide que JavaScript ejecutado en https://mi-frontend.com lea la respuesta de https://mi-api.com porque son orígenes distintos. Esta política evita que un sitio lea datos de otro sin permiso. CORS es la forma en que el servidor declara qué orígenes pueden acceder a su respuesta.
Esto solo aplica cuando frontend y backend están en orígenes distintos (dominios, puertos o protocolos distintos) — una API Express separada del frontend (típico si el frontend es una SPA aparte) necesita CORS; una app full-stack donde todo se sirve desde el mismo origen (como Astro o Next.js, ver sus respectivas guías) generalmente no.
Instalación y uso básico
Sección titulada «Instalación y uso básico»pnpm add corsbun add corsnpm install corsimport cors from "cors"import express from "express"
const app = express()
app.use(cors()) // permite CUALQUIER origen — cómodo en desarrollo, rara vez correcto en producciónRestringir a orígenes específicos
Sección titulada «Restringir a orígenes específicos»app.use( cors({ origin: "https://mi-frontend.com", credentials: true // necesario si el frontend manda cookies (ver Cookies vs sesiones) }))// Varios orígenes permitidos (ej: producción + preview de Vercel)app.use( cors({ origin: ["https://mi-frontend.com", "https://staging.mi-frontend.com"] }))Origen dinámico (validado a mano)
Sección titulada «Origen dinámico (validado a mano)»const origenesPermitidos = ["https://mi-frontend.com", "http://localhost:5173"]
app.use( cors({ origin: (origin, callback) => { if (!origin || origenesPermitidos.includes(origin)) { callback(null, true) } else { callback(new Error("Origen no permitido por CORS")) } } }))CORS por ruta, no global
Sección titulada «CORS por ruta, no global»app.get("/publico", cors(), handler) // esta ruta sí permite cross-originapp.get("/interno", handler) // esta no (sin el middleware cors())Configuración rápida
Sección titulada «Configuración rápida»Opción de cors({...}) | Qué controla |
|---|---|
origin | Qué origen(es) puede leer la respuesta |
credentials: true | Permite mandar cookies/auth headers en la request cross-origin |
methods | Qué verbos HTTP permite (GET, POST, etc.) |
allowedHeaders | Qué headers custom puede mandar el cliente |
Lo que CORS no resuelve
Sección titulada «Lo que CORS no resuelve»cors()sin opciones (origin: '*'implícito) es válido para una API pública sin autenticación, pero incompatible concredentials: true— el navegador rechaza esa combinación por diseño (no se puede permitir cookies desde “cualquier origen”).- CORS protege al navegador, no al servidor — no es una medida de seguridad contra requests hechas fuera del navegador (curl, Postman, otro servidor); para eso hace falta autenticación real, ver Middleware de autenticación.
- Un error de CORS en la consola del navegador (
blocked by CORS policy) es del lado del cliente — la request en general sí llegó al servidor y se ejecutó; lo que se bloquea es que el navegador le entregue la respuesta al código JS que la pidió.