¿Qué es el error CORS?
Si has construido un frontend con React que se comunica con un backend en Express.js, es casi seguro que has visto este error en la consola del navegador:
Access to fetch at 'http://localhost:3001/api/data' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.
CORS significa Cross-Origin Resource Sharing (Intercambio de recursos entre orígenes). Es un mecanismo de seguridad del navegador que restringe las solicitudes HTTP realizadas desde un origen (dominio + puerto + protocolo) hacia un origen diferente. El navegador es quien aplica esta política, no el servidor. Cuando una solicitud cruza orígenes, el navegador primero verifica si el servidor lo permite buscando encabezados HTTP específicos en la respuesta, principalmente Access-Control-Allow-Origin.
Si esos encabezados faltan o son incorrectos, el navegador bloquea la respuesta y lanza el error CORS, incluso si el servidor devolvió un 200 OK. Esto no es un bug de Express. Es el navegador haciendo exactamente lo que debe hacer.
¿Por qué ocurre CORS en React + Express?
En una configuración típica de desarrollo con React + Express, ejecutas dos servidores separados:
- Servidor de desarrollo de React:
http://localhost:3000(Create React App) ohttp://localhost:5173(Vite) - Servidor API de Express:
http://localhost:3001(u otro puerto)
Como los puertos son diferentes, el navegador los trata como orígenes distintos. Cualquier llamada fetch o axios desde tu app de React hacia la API de Express es una solicitud de origen cruzado, y el navegador la bloqueará a menos que tu servidor Express lo permita explícitamente.
En producción, el problema suele desaparecer si despliegas ambos en el mismo dominio (por ejemplo, Next.js sirviendo ambos, o un reverse proxy con Nginx enrutando /api hacia Express), pero durante el desarrollo, los errores CORS están casi garantizados si ejecutas los dos servidores por separado.
Solución rápida: instalar el middleware cors
La forma más rápida de solucionar CORS en Express es usar el paquete oficial cors de npm. Instálalo:
npm install cors
Luego agrégalo a tu aplicación Express antes de cualquier definición de rutas:
// server.js (o app.js)
const express = require('express');
const cors = require('cors');
const app = express();
// Permitir todos los orígenes — solo usar en desarrollo
app.use(cors());
app.use(express.json());
app.get('/api/data', (req, res) => {
res.json({ message: 'Hello from Express!' });
});
app.listen(3001, () => {
console.log('Server running on http://localhost:3001');
});
Llamar a app.use(cors()) sin argumentos agrega Access-Control-Allow-Origin: * a cada respuesta, lo que permite solicitudes desde cualquier origen. Esto está bien para desarrollo local pero no debe usarse en producción para APIs que manejan datos autenticados.
Configuración para producción
Para producción, siempre especifica exactamente qué orígenes están permitidos, y configura credenciales, métodos y encabezados de forma explícita:
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = [
'https://www.yourdomain.com',
'https://yourdomain.com',
'http://localhost:3000', // Mantener para desarrollo local
'http://localhost:5173', // Servidor de desarrollo de Vite
];
// Permitir solicitudes sin origen (ej: curl, Postman, servidor a servidor)
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error(`CORS blocked for origin: ${origin}`));
}
},
credentials: true, // Permitir cookies y encabezados Authorization
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
exposedHeaders: ['Content-Range', 'X-Content-Range'],
maxAge: 86400, // Cachear respuesta preflight por 24 horas
};
app.use(cors(corsOptions));
// Manejar explícitamente solicitudes preflight para todas las rutas
app.options('*', cors(corsOptions));
Algunos puntos clave sobre esta configuración:
credentials: true— Necesario si tu app de React envía cookies o encabezadosAuthorization. Cuando esto está configurado,originno puede ser*; debe ser un origen específico.app.options('*', cors())— Los navegadores envían una solicitud HTTP OPTIONS (preflight) antes de cualquier solicitud no simple (PUT, PATCH, DELETE, o solicitudes con encabezados personalizados). Express debe responder a estas, o la solicitud real será bloqueada.maxAge— Indica al navegador cuánto tiempo cachear la respuesta preflight, reduciendo solicitudes OPTIONS innecesarias.
Alternativa con proxy de React para desarrollo
Si estás usando Create React App, puedes evitar CORS completamente durante el desarrollo agregando un campo proxy a tu package.json. Esto le indica al servidor de desarrollo webpack que reenvíe las solicitudes de API a tu backend Express:
// package.json (raíz del proyecto React)
{
"name": "my-react-app",
"version": "1.0.0",
"proxy": "http://localhost:3001",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build"
}
}
Ahora, cuando tu código React llame a fetch('/api/data') (nota: sin hostname), el servidor de desarrollo redirigirá la solicitud a http://localhost:3001/api/data — mismo origen, sin CORS. Este enfoque solo funciona en desarrollo; en producción tu despliegue debe manejar el enrutamiento.
Configuración de proxy en Vite
Si estás usando Vite (la alternativa moderna a Create React App), configura el proxy en vite.config.js:
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 5173,
proxy: {
// Todas las solicitudes que comienzan con /api se reenvían a Express
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
secure: false,
// Opcional: reescribir ruta si tus rutas Express no comienzan con /api
// rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
});
Con esta configuración, una llamada a fetch('/api/users') en tu componente React se redirige a http://localhost:3001/api/users. No se necesitan encabezados CORS durante el desarrollo. Tu servidor Express se mantiene limpio.
Errores comunes
1. Usar origen wildcard con credenciales
Esta combinación siempre falla. Los navegadores rechazarán una respuesta CORS que tenga Access-Control-Allow-Origin: * junto con Access-Control-Allow-Credentials: true:
// Esto NO funcionará — el navegador rechaza wildcard + credenciales
app.use(cors({
origin: '*', // Wildcard
credentials: true, // No se pueden combinar
}));
Solución: reemplaza '*' con un origen específico o una función que valide orígenes, como se muestra en la configuración de producción anterior.
2. Falta el manejador OPTIONS para preflight
Si tu aplicación Express no maneja solicitudes OPTIONS, las verificaciones preflight para PUT, PATCH, DELETE y solicitudes con encabezados personalizados recibirán un 404, y la solicitud real nunca se enviará. Siempre agrega app.options('*', cors(corsOptions)) antes de tus definiciones de rutas.
3. Configurar encabezados CORS manualmente y con el paquete cors
Configurar doblemente el encabezado Access-Control-Allow-Origin hace que el navegador vea un valor duplicado y bloquee la solicitud. Elige un enfoque — el middleware cors o encabezados manuales — y mantente con él. Nunca hagas ambos.
4. Middleware CORS agregado después de las rutas
El middleware en Express se ejecuta en el orden en que se registra. Si agregas app.use(cors()) después de tus manejadores de rutas, las rutas se procesan primero y los encabezados CORS nunca se agregan. Siempre coloca el middleware CORS al inicio de tu pila de middleware.
Depuración de errores CORS
Cuando encuentras un error CORS, el mensaje en la consola del navegador te indica qué encabezado falta o no coincide. Revisar la pestaña Network en DevTools muestra los encabezados de respuesta reales devueltos por tu servidor Express — compáralos con lo esperado.
Cosas comunes a verificar:
- ¿Está presente
Access-Control-Allow-Originen la respuesta? - ¿El origen en el encabezado coincide exactamente con el origen de tu app React (incluyendo protocolo y puerto)?
- Para solicitudes con credenciales: ¿está presente
Access-Control-Allow-Credentials: true? - Para solicitudes PUT/DELETE: ¿el preflight OPTIONS devolvió un 200 con
Access-Control-Allow-Methods? - ¿Estás enviando un encabezado personalizado (como
Authorization)? Debe aparecer enAccess-Control-Allow-Headers.
Depura tu error CORS al instante
Pega tu mensaje de error CORS y obtén el código de solución exacto para Express, Nginx, Apache y más — sin adivinar.
Abrir depurador de errores CORS →Herramientas de desarrollo relacionadas
- Depurador de errores CORS — pega tu error, obtén código de solución para Express, Nginx, Apache y más
- Decodificador JWT — inspecciona encabezados Authorization y payloads JWT
- Códigos de estado HTTP — entiende 404, 403, 401, 500 y todos los demás códigos
- Inspector ENV — valida variables de entorno para tu app Express
- Ver todas las herramientas gratuitas para desarrolladores