How to Fix CORS Error in Express + React (2026 Guide)

Published March 2026 • 8 min read

What is the CORS Error?

If you have built a React frontend that talks to an Express.js backend, you have almost certainly seen this error in your browser console:

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 stands for Cross-Origin Resource Sharing. It is a browser security mechanism that restricts HTTP requests made from one origin (domain + port + protocol) to a different origin. The browser enforces this policy — not the server. When a request crosses origins, the browser first checks whether the server allows it by looking for specific HTTP response headers, primarily Access-Control-Allow-Origin.

If those headers are missing or incorrect, the browser blocks the response and throws the CORS error — even if the server returned a 200 OK. This is not an Express bug. It is the browser doing exactly what it is supposed to do.

Why Does CORS Happen in React + Express?

In a typical React + Express development setup, you run two separate servers:

  • React dev server: http://localhost:3000 (Create React App) or http://localhost:5173 (Vite)
  • Express API server: http://localhost:3001 (or another port)

Because the ports differ, the browser treats these as different origins. Any fetch or axios call from your React app to the Express API is a cross-origin request, and the browser will block it unless your Express server explicitly allows it.

In production the problem often disappears — if you deploy both on the same domain (e.g., Next.js serving both, or an Nginx reverse proxy routing /api to Express) — but during development, CORS errors are almost guaranteed if you run the two servers separately.

Quick Fix: Install the cors Middleware

The fastest way to fix CORS in Express is to use the official cors npm package. Install it:

npm install cors

Then add it to your Express app before any route definitions:

// server.js (or app.js)
const express = require('express');
const cors = require('cors');

const app = express();

// Allow all origins — only use this in development
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');
});

Calling app.use(cors()) with no arguments adds Access-Control-Allow-Origin: * to every response, which permits requests from any origin. This is fine for local development but must not be used in production for APIs that handle authenticated data.

Production Configuration

For production, always specify exactly which origins are allowed, and configure credentials, methods, and headers explicitly:

const corsOptions = {
  origin: function (origin, callback) {
    const allowedOrigins = [
      'https://www.yourdomain.com',
      'https://yourdomain.com',
      'http://localhost:3000',   // Keep for local dev
      'http://localhost:5173',   // Vite dev server
    ];
    // Allow requests with no origin (e.g., curl, Postman, server-to-server)
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error(`CORS blocked for origin: ${origin}`));
    }
  },
  credentials: true,           // Allow cookies and Authorization headers
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
  exposedHeaders: ['Content-Range', 'X-Content-Range'],
  maxAge: 86400,               // Cache preflight response for 24 hours
};

app.use(cors(corsOptions));

// Explicitly handle preflight requests for all routes
app.options('*', cors(corsOptions));

A few key points about this configuration:

  • credentials: true — Required if your React app sends cookies or Authorization headers. When this is set, origin cannot be *; it must be a specific origin.
  • app.options('*', cors()) — Browsers send an HTTP OPTIONS preflight request before any non-simple request (PUT, PATCH, DELETE, or requests with custom headers). Express must respond to these, or the actual request will be blocked.
  • maxAge — Tells the browser how long to cache the preflight response, reducing unnecessary OPTIONS requests.

React Proxy Alternative for Development

If you are using Create React App, you can avoid CORS entirely during development by adding a proxy field to your package.json. This tells the webpack dev server to forward API requests to your Express backend:

// package.json (React project root)
{
  "name": "my-react-app",
  "version": "1.0.0",
  "proxy": "http://localhost:3001",
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build"
  }
}

Now, when your React code calls fetch('/api/data') (note: no hostname), the dev server proxies the request to http://localhost:3001/api/data — same origin, no CORS. This approach only works in development; in production your deployment must handle routing.

Vite Proxy Configuration

If you are using Vite (the modern alternative to Create React App), configure the proxy in 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: {
      // All requests starting with /api are forwarded to Express
      '/api': {
        target: 'http://localhost:3001',
        changeOrigin: true,
        secure: false,
        // Optional: rewrite path if your Express routes don't start with /api
        // rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
});

With this configuration, a call to fetch('/api/users') in your React component is proxied to http://localhost:3001/api/users. No CORS headers needed during development. Your Express server stays clean.

Common Mistakes

1. Using wildcard origin with credentials

This combination always fails. Browsers will reject a CORS response that has Access-Control-Allow-Origin: * alongside Access-Control-Allow-Credentials: true:

// This will NOT work — browser rejects wildcard + credentials
app.use(cors({
  origin: '*',          // Wildcard
  credentials: true,    // Cannot combine these
}));

Fix: replace '*' with a specific origin string or a function that validates origins, as shown in the production configuration above.

2. Missing OPTIONS handler for preflight

If your Express app does not handle OPTIONS requests, preflight checks for PUT, PATCH, DELETE, and custom-header requests will receive a 404, and the actual request is never sent. Always add app.options('*', cors(corsOptions)) before your route definitions.

3. Setting CORS headers manually and with the cors package

Double-setting the Access-Control-Allow-Origin header causes the browser to see a duplicate value and block the request. Choose one approach — the cors middleware or manual headers — and stick with it. Never do both.

4. CORS middleware added after routes

Middleware in Express runs in the order it is registered. If you add app.use(cors()) after your route handlers, the routes are matched first and the CORS headers are never added. Always place the CORS middleware at the top of your middleware stack.

Debugging CORS Errors

When you encounter a CORS error, the browser console message tells you which header is missing or mismatched. Checking the Network tab in DevTools shows the actual response headers returned by your Express server — compare them against what is expected.

Common things to verify:

  • Is Access-Control-Allow-Origin present on the response?
  • Does the origin in the header exactly match your React app's origin (including protocol and port)?
  • For credentialed requests: is Access-Control-Allow-Credentials: true present?
  • For PUT/DELETE requests: did the OPTIONS preflight return a 200 with Access-Control-Allow-Methods?
  • Are you sending a custom header (like Authorization)? It must appear in Access-Control-Allow-Headers.

Debug Your CORS Error Instantly

Paste your CORS error message and get the exact fix code for Express, Nginx, Apache, and more — no guessing required.

Open CORS Error Debugger →

Related Developer Tools