How to Fix CORS Errors in React with a Node.js Express Backend

If you’ve landed here, chances are your browser console just threw something like “Access to XMLHttpRequest at ‘http://localhost:5000/api’ from origin ‘http://localhost:3000’ has been blocked by CORS policy”. You’re not alone. The CORS error in React with a Node.js Express backend is one of the most googled frustrations in modern web development.

This guide is built differently from the typical Stack Overflow thread: instead of throwing a snippet at you, we’ll walk through why the error happens, which exact line to add depending on your situation, and how to avoid the silent traps (credentials, preflight, wildcard mismatch) that keep developers stuck for hours.

What a CORS Error Actually Means

CORS stands for Cross-Origin Resource Sharing. It’s not a React bug, not an Express bug, and not even a JavaScript bug. It’s a browser security policy called the same-origin policy.

When your React app runs on http://localhost:3000 and tries to fetch data from your Express API on http://localhost:5000, the browser sees two different origins (different ports = different origins) and blocks the response unless the server explicitly says “yes, this origin is allowed.”

The three things that define an “origin”

  • Protocol (http vs https)
  • Domain (localhost, santiance.com, api.example.com)
  • Port (3000, 5000, 8080)

If any one of them differs between the frontend and the backend, the browser treats it as cross-origin.

browser error code

Common CORS Error Messages and What They Mean

Error Message Real Cause
No ‘Access-Control-Allow-Origin’ header is present CORS middleware not installed or not loaded before routes
The ‘Access-Control-Allow-Origin’ header has a value ‘*’ that is not equal to the supplied origin You’re using wildcard * while sending credentials
Response to preflight request doesn’t pass access control check OPTIONS method or custom header not allowed
Method PATCH/PUT/DELETE is not allowed methods option missing in cors config
Credentials flag is true, but Access-Control-Allow-Credentials is not ‘true’ withCredentials enabled on client but not on server

The Basic Fix: Installing the cors Middleware

In 95% of cases, the fix is simply installing and properly configuring the official cors package on your Express server.

  1. Open your terminal in the backend folder
  2. Run npm install cors
  3. Open your main server file (usually server.js or app.js)
  4. Add the middleware before any of your routes
const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors()); // <-- must come BEFORE routes
app.use(express.json());

app.get('/api/users', (req, res) => {
  res.json({ users: ['Alice', 'Bob'] });
});

app.listen(5000, () => console.log('API running on 5000'));

Warning: Calling app.use(cors()) with no options allows every origin. That’s fine in development but dangerous in production.

Production-Ready CORS Configuration

For a real deployment, you should restrict CORS to your actual React frontend domain.

const allowedOrigins = [
  'http://localhost:3000',
  'https://app.santiance.com'
];

app.use(cors({
  origin: function (origin, callback) {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
}));

Why a function instead of an array?

Using a function lets you return the exact requesting origin in the Access-Control-Allow-Origin header instead of a wildcard, which is required when credentials: true is set.

browser error code

Handling Preflight Requests (OPTIONS)

Whenever your React app sends a request with:

  • Methods like PUT, PATCH, DELETE
  • Custom headers like Authorization or Content-Type: application/json
  • Cookies or credentials

The browser first fires an OPTIONS preflight request to check whether the actual request is safe to send. If your server doesn’t respond correctly to OPTIONS, you get the dreaded “Response to preflight request doesn’t pass access control check”.

The cors middleware handles preflight automatically when applied globally. But if you only apply CORS to specific routes, you must enable it for OPTIONS too:

app.options('*', cors()); // enable preflight for all routes

Dealing with Credentials (Cookies, JWT in Cookies, Sessions)

This is where most developers get stuck. There are two sides that must agree.

On the React side (using axios)

import axios from 'axios';

const api = axios.create({
  baseURL: 'https://api.santiance.com',
  withCredentials: true // <-- send cookies
});

Or with fetch

fetch('https://api.santiance.com/me', {
  credentials: 'include'
});

On the Express side

app.use(cors({
  origin: 'https://app.santiance.com', // NOT '*'
  credentials: true
}));

Critical rule: When credentials: true, the origin value must be a specific origin (or a function that returns one). Using '*' will silently fail.

The React Dev-Only Workaround: The Proxy

If you only need to get past CORS during local development (e.g. while testing with Create React App or Vite), you can use a proxy. The browser thinks everything comes from the same origin.

Create React App

Add this to your package.json:

"proxy": "http://localhost:5000"

Vite

// vite.config.js
export default {
  server: {
    proxy: {
      '/api': 'http://localhost:5000'
    }
  }
}

Important: The proxy is a development convenience only. In production you still need correctly configured CORS on the backend.

browser error code

Troubleshooting Checklist

Before you spend another hour reading GitHub issues, run through this list:

  1. Did you call app.use(cors()) before defining your routes?
  2. Did you restart your Node server after the change?
  3. Are you mixing origin: '*' with credentials: true? Don't.
  4. If using cookies, is withCredentials: true set on the client?
  5. Is the Network tab showing an OPTIONS request? Did it return 204 or 200?
  6. Are you hitting the right URL? A 404 on the backend can look like a CORS error in the browser.
  7. Is your reverse proxy (Nginx, Cloudflare) stripping the CORS headers? Check the raw response.
  8. If using HTTPS in production, make sure cookies have SameSite=None; Secure.

Bonus: When CORS Is Not the Real Problem

A surprising number of "CORS errors" are actually:

  • The backend is down or unreachable, so no headers are returned at all
  • The backend crashed mid-request and Express never reached the cors middleware
  • A typo in the API URL (https vs http, missing port)
  • An overly strict Content Security Policy blocking the request

Always check the backend logs and the raw Network response before assuming it's CORS.

FAQ

Why does CORS only block the response, not the request?

The browser actually does send the request to the server (for simple requests at least), but it prevents the JavaScript from reading the response unless the proper headers are present. That's why your backend logs may show the hit even when the browser shows a CORS error.

Do I need CORS if my React app and Node.js API are on the same domain?

No. If both are served from the exact same protocol, domain, and port (for example via the same reverse proxy), there is no cross-origin and no CORS configuration is needed.

Is disabling CORS in the browser a real solution?

No. Browser extensions or flags that disable CORS only work on your own machine and create serious security risks. They are not a fix, they are a debugging hack.

Why does my GET request work but POST fails?

POST requests with Content-Type: application/json trigger a preflight OPTIONS request. If your Express server doesn't handle OPTIONS properly (usually because cors middleware wasn't registered globally), POSTs fail while simple GETs pass.

Can I fix CORS only on the frontend?

Not really. CORS is enforced by the browser based on headers sent by the server. The only frontend-only workaround is a dev proxy, which shifts the request to look same-origin.

What's the difference between cors() and manually setting headers?

The cors package handles preflight, headers, methods, and credentials in one place. Manually setting res.header('Access-Control-Allow-Origin', ...) works but is error-prone and easy to forget for OPTIONS requests.

Conclusion

The CORS error between React and Node.js Express looks intimidating but almost always comes down to three things: installing the cors middleware, placing it before your routes, and matching the credentials setting on both sides. Once you understand it's the browser (not Express, not React) enforcing the rule, debugging becomes much faster.

Save this checklist, share it with your team, and the next time someone pastes a CORS error into Slack, you'll have the answer ready in 30 seconds.

Leave a Comment