How to Fix CORS Errors in Express.js: Causes and Solutions Explained

How to Fix CORS Errors in Express.js: Causes and Solutions Explained

by | Apr 8, 2026 | Uncategorized | 0 comments

Why You Are Seeing a CORS Error in Express.js

If you are reading this, you are probably staring at a browser console message that looks something like this:

Access to XMLHttpRequest at 'http://localhost:5000/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.

You are not alone. CORS errors are one of the most common blockers developers face when building APIs with Express.js. The good news is that every single CORS scenario has a clear, testable fix. This guide covers all of them.

We will walk through what CORS actually is, the most common causes of CORS errors in Express.js, and exact code solutions you can paste into your project right now. Whether you are dealing with preflight requests, cookies, wildcard origins, or a stubborn OPTIONS route, you will find the answer below.

What Is CORS and Why Does the Browser Block Requests?

CORS stands for Cross-Origin Resource Sharing. It is a security mechanism built into web browsers. When your frontend (running on one origin, like http://localhost:3000) tries to make a request to your Express.js backend (running on a different origin, like http://localhost:5000), the browser checks whether the server explicitly allows that cross-origin request.

If the server does not include the correct Access-Control-Allow-Origin header in its response, the browser blocks the request. This is the CORS error.

Key point: CORS errors are enforced by the browser, not by Express.js or Node.js. Your server still receives and processes the request. The browser simply refuses to hand the response to your JavaScript code.

Quick Fix: Install and Use the cors Middleware

The fastest way to fix a CORS error in Express.js is to use the official cors npm package. This is the approach recommended in the Express.js documentation itself.

Step 1: Install the Package

npm install cors

Step 2: Add It to Your Express App

const express = require('express');
const cors = require('cors');

const app = express();

// Allow all origins (development only!)
app.use(cors());

app.get('/api/data', (req, res) => {
  res.json({ message: 'CORS is working' });
});

app.listen(5000, () => {
  console.log('Server running on port 5000');
});

This will add the Access-Control-Allow-Origin: * header to every response, which fixes the error for basic requests during development.

But if your situation is more complex (credentials, specific origins, preflight), keep reading.

All Common Causes of CORS Errors in Express.js (and How to Fix Each One)

Below is a complete reference table of the most frequent CORS error scenarios in Express.js, their causes, and their solutions.

Scenario Cause Solution
No CORS headers at all cors middleware not installed or not applied Install and use cors() middleware
Preflight (OPTIONS) request fails Server does not respond to OPTIONS method Enable preflight with cors() or handle OPTIONS manually
Credentials not allowed Wildcard origin (*) used with credentials: true Specify an explicit origin instead of *
Trailing slash in origin Origin string has a trailing / that does not match Remove trailing slash from allowed origin string
CORS works for GET but not POST/PUT/DELETE Preflight not handled, or allowed methods not set Configure methods option in cors middleware
Custom headers blocked Custom request headers not listed in allowedHeaders Add custom headers to allowedHeaders config
CORS error only in production Frontend origin is different from what is allowed Use environment variables for dynamic origin

Let us now solve each of these one by one.

Fix 1: Allowing a Specific Origin (Not Wildcard)

Using * as the allowed origin is fine for quick testing but not recommended for production. Here is how to allow a specific frontend origin:

const cors = require('cors');

const corsOptions = {
  origin: 'https://myapp.example.com'
};

app.use(cors(corsOptions));

This tells Express.js to include Access-Control-Allow-Origin: https://myapp.example.com in the response headers.

Allowing Multiple Origins

If you need to allow more than one origin (for example, a staging and production domain), pass a function:

const allowedOrigins = [
  'https://myapp.example.com',
  'https://staging.myapp.example.com',
  'http://localhost:3000'
];

const corsOptions = {
  origin: function (origin, callback) {
    // Allow requests with no origin (like mobile apps or Postman)
    if (!origin) return callback(null, true);
    if (allowedOrigins.indexOf(origin) !== -1) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  }
};

app.use(cors(corsOptions));

Fix 2: Handling Preflight (OPTIONS) Requests

When the browser sends a request with certain characteristics (custom headers, PUT/DELETE method, or content type other than application/x-www-form-urlencoded, multipart/form-data, or text/plain), it first sends a “preflight” OPTIONS request to ask the server for permission.

If your server does not respond correctly to this OPTIONS request, the actual request never gets made.

Solution A: Let the cors Middleware Handle It (Recommended)

The cors package handles preflight automatically when used as application-level middleware with app.use(cors()). But if you need to enable preflight for a specific route:

// Enable preflight for a specific route
app.options('/api/data', cors());
app.post('/api/data', cors(), (req, res) => {
  res.json({ status: 'ok' });
});

Or enable preflight globally for all routes:

app.options('*', cors());

Solution B: Handle OPTIONS Manually (Without the cors Package)

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'https://myapp.example.com');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');

  if (req.method === 'OPTIONS') {
    return res.sendStatus(204);
  }
  next();
});

This manually sets the required headers and immediately responds with a 204 No Content for preflight requests.

Fix 3: Enabling Credentials (Cookies, Authorization Headers)

If your frontend sends cookies or uses Authorization headers, you need to enable credentials on both sides.

On the Express.js Server

const corsOptions = {
  origin: 'https://myapp.example.com',
  credentials: true
};

app.use(cors(corsOptions));

On the Frontend (Fetch API Example)

fetch('https://api.example.com/data', {
  method: 'GET',
  credentials: 'include'
});

On the Frontend (Axios Example)

axios.get('https://api.example.com/data', {
  withCredentials: true
});

Critical rule: When credentials: true is set, you cannot use * as the origin. The browser will reject the response with an error like:

The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.

You must specify the exact origin string.

Fix 4: The Trailing Slash Trap

This is a subtle but surprisingly common bug. If your allowed origin includes a trailing slash, the match will fail:

// WRONG: trailing slash causes a mismatch
origin: 'https://myapp.example.com/'

// CORRECT: no trailing slash
origin: 'https://myapp.example.com'

The browser sends the Origin header without a trailing slash. If your configuration includes one, Express.js will not match it, and you will get a CORS error even though everything else looks correct.

Fix 5: Allowing Custom Headers

If your frontend sends custom headers (for example, X-Request-ID or a custom auth token header), you must explicitly allow them:

const corsOptions = {
  origin: 'https://myapp.example.com',
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID']
};

app.use(cors(corsOptions));

If you skip this, the browser preflight check will fail because the server does not advertise that it accepts those headers.

Fix 6: Allowing Specific HTTP Methods

By default, the cors middleware allows simple methods (GET, HEAD, PUT, PATCH, POST, DELETE). If you need to be explicit or restrictive:

const corsOptions = {
  origin: 'https://myapp.example.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE']
};

app.use(cors(corsOptions));

Fix 7: Dynamic Origin Using Environment Variables

For projects that deploy to multiple environments (development, staging, production), hardcoding origins is not practical. Use environment variables instead:

// .env file
ALLOWED_ORIGINS=https://myapp.example.com,https://staging.myapp.example.com
require('dotenv').config();

const allowedOrigins = process.env.ALLOWED_ORIGINS.split(',');

const corsOptions = {
  origin: function (origin, callback) {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true
};

app.use(cors(corsOptions));

This is the production-ready pattern. It keeps your CORS configuration flexible and secure.

Fix 8: CORS Error Only Happens in the Browser (Not Postman)

If your API works perfectly in Postman or cURL but fails in the browser, that is expected behavior. CORS is a browser-only security feature. Server-to-server requests and tools like Postman do not enforce CORS.

This confirms that the issue is not with your Express.js logic. It is purely about the response headers your server sends back to the browser.

Fix 9: Middleware Order Matters

One often-overlooked cause of CORS failures in Express.js is middleware ordering. The cors() middleware must run before your route handlers. If another middleware sends a response before cors() adds its headers, the CORS headers will be missing.

// CORRECT order
app.use(cors(corsOptions));
app.use(express.json());
app.use('/api', apiRouter);

// WRONG order: routes before cors
app.use('/api', apiRouter);
app.use(cors(corsOptions));

Complete Production-Ready CORS Configuration

Here is a full example combining all the best practices into a single, copy-paste-ready configuration:

const express = require('express');
const cors = require('cors');
require('dotenv').config();

const app = express();

const allowedOrigins = process.env.ALLOWED_ORIGINS
  ? process.env.ALLOWED_ORIGINS.split(',')
  : ['http://localhost:3000'];

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

// Apply CORS before any routes
app.use(cors(corsOptions));

// Handle preflight globally
app.options('*', cors(corsOptions));

app.use(express.json());

app.get('/api/data', (req, res) => {
  res.json({ message: 'Everything is working' });
});

app.listen(process.env.PORT || 5000, () => {
  console.log('Server is running');
});

Debugging Checklist

If you have tried everything and the CORS error persists, run through this checklist:

  1. Check the browser console carefully. The error message tells you exactly which header is missing or misconfigured.
  2. Inspect the response headers in the Network tab of your browser DevTools. Look for Access-Control-Allow-Origin.
  3. Verify the origin string exactly matches. No trailing slash. Correct protocol (http vs https). Correct port number.
  4. Check middleware order. The cors middleware must be applied before your routes.
  5. Confirm your server is actually running on the expected port. A connection refusal can sometimes look like a CORS error.
  6. Look for a reverse proxy (like Nginx or Apache) that might be stripping CORS headers before they reach the browser.
  7. Check for duplicate headers. If both your code and a proxy add CORS headers, the browser may reject the response for having two Access-Control-Allow-Origin values.

CORS vs. Proxy: When to Use a Frontend Proxy Instead

In development, an alternative to configuring CORS is to use a frontend proxy. For example, if you use React with Vite or Create React App, you can proxy API requests through the dev server so that the browser thinks the API is on the same origin.

Vite example (vite.config.js):

export default {
  server: {
    proxy: {
      '/api': 'http://localhost:5000'
    }
  }
};

This eliminates CORS errors during development because the browser sends the request to the same origin. However, you will still need proper CORS configuration for production unless your frontend and API share the same domain.

Frequently Asked Questions

What does CORS stand for?

CORS stands for Cross-Origin Resource Sharing. It is a protocol that allows servers to declare which origins are permitted to access their resources via a web browser.

Why does my API work in Postman but not in the browser?

Postman does not enforce the Same-Origin Policy. Browsers do. CORS errors only occur in browser environments because the browser checks for the proper response headers before allowing JavaScript to read the response.

Can I just use Access-Control-Allow-Origin: * in production?

You can, but it is not recommended. Using a wildcard allows any website to make requests to your API. This is a security risk, especially if your API handles sensitive data. It also prevents you from using credentials: true.

Why do I get a CORS error on POST but not GET?

POST requests with a Content-Type of application/json trigger a preflight OPTIONS request. If your server does not handle this preflight correctly, the POST will be blocked. GET requests with simple headers do not trigger preflight.

Do I need the cors npm package, or can I set headers manually?

You do not strictly need the package. You can set the headers manually using res.header(). However, the cors package handles many edge cases (like preflight, dynamic origins, and proper header formatting) that are easy to get wrong manually.

I added cors() but still get the error. What is wrong?

The most common causes are: wrong middleware order (cors must come before your routes), a proxy or load balancer stripping headers, a trailing slash mismatch in the origin, or the frontend sending credentials while the server uses a wildcard origin.

Does CORS protect my API from unauthorized access?

No. CORS is a browser security feature, not an API security mechanism. Anyone can still call your API directly from a server, script, or tool like cURL. To secure your API, use authentication, rate limiting, and proper authorization.