If you’ve ever built a Node.js API and watched your frontend explode with a red “Access-Control-Allow-Origin” error in the browser console, you already know how frustrating CORS can be. The good news: fixing CORS errors in Node.js is straightforward once you understand what the browser is actually asking for.
In this guide, we walk through what CORS is, why Express keeps throwing these errors, and give you copy-paste ready fixes for every common scenario: credentials, multiple origins, dynamic origins, and preflight requests.
What Is CORS and Why Does Node.js Throw These Errors?
CORS (Cross-Origin Resource Sharing) is a browser security mechanism. When your frontend at https://app.example.com tries to call your API at https://api.example.com, the browser blocks the request unless the server explicitly says “yes, this origin is allowed” via HTTP headers.
Node.js and Express do not add these headers by default. That’s why the moment your frontend and backend run on different origins (different domain, subdomain, or even port), the browser refuses the response.
An Origin = Protocol + Domain + Port
| Frontend | Backend | Same Origin? |
|---|---|---|
| http://localhost:3000 | http://localhost:5000 | No (different port) |
| https://app.site.com | https://api.site.com | No (different subdomain) |
| https://site.com | http://site.com | No (different protocol) |

Common CORS Error Messages (And What They Really Mean)
Here are the exact console errors developers see, translated into plain English:
- “Access to XMLHttpRequest at ‘…’ from origin ‘…’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.”
Your server didn’t send the CORS header at all. Fix: enable thecorsmiddleware. - “Response to preflight request doesn’t pass access control check”
The browser sent anOPTIONSrequest first and your server didn’t respond correctly. - “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’re sending cookies but usingorigin: '*'. You must specify an exact origin. - “Method PATCH is not allowed by Access-Control-Allow-Methods”
You need to explicitly allow that HTTP method. - “Request header field authorization is not allowed by Access-Control-Allow-Headers”
Your custom header isn’t whitelisted.
How to Fix CORS Errors in Node.js: Step-by-Step
Step 1: Install the cors Middleware
The official Express cors package handles 95% of use cases. Install it:
npm install cors
Step 2: The Quick Fix (Development Only)
If you just want to make it work locally, allow all origins:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors()); // Allows all origins
app.get('/api/data', (req, res) => {
res.json({ message: 'CORS is working' });
});
app.listen(5000);
Warning: Never ship app.use(cors()) to production without restrictions. It opens your API to any website on the internet.
Step 3: Production Setup with a Specific Origin
app.use(cors({
origin: 'https://app.yourdomain.com',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization']
}));

Scenario 1: Allowing Multiple Origins
You probably need to allow localhost during development and your production domain at the same time. Use an array or a function:
const allowedOrigins = [
'http://localhost:3000',
'http://localhost:5173',
'https://app.yourdomain.com',
'https://staging.yourdomain.com'
];
app.use(cors({
origin: function (origin, callback) {
// Allow requests with no origin (mobile apps, curl, Postman)
if (!origin) return callback(null, true);
if (allowedOrigins.includes(origin)) {
return callback(null, true);
}
return callback(new Error('Not allowed by CORS'));
}
}));
Scenario 2: Sending Cookies or Authorization Headers (Credentials)
If your frontend sends cookies or auth tokens, you need credentials: true and you cannot use the * wildcard.
Backend (Express)
app.use(cors({
origin: 'https://app.yourdomain.com',
credentials: true
}));
Frontend (fetch)
fetch('https://api.yourdomain.com/user', {
method: 'GET',
credentials: 'include'
});
Frontend (axios)
axios.get('https://api.yourdomain.com/user', {
withCredentials: true
});
Scenario 3: Fixing Preflight (OPTIONS) Requests
Browsers send a preflight OPTIONS request before any “non-simple” request (PUT, DELETE, PATCH, or requests with custom headers like Authorization).
The cors package handles this automatically, but if you have custom middleware or authentication running before it, the preflight can be rejected. The fix is to make sure CORS runs before your auth middleware:
// Correct order
app.use(cors(corsOptions));
app.use(express.json());
app.use(authMiddleware); // auth AFTER cors
app.use('/api', routes);
You can also explicitly enable preflight for all routes:
app.options('*', cors(corsOptions));

Scenario 4: Per-Route CORS Configuration
Sometimes you want a public endpoint (open to everyone) and private endpoints (restricted). Apply CORS per route:
// Public endpoint - open to all
app.get('/api/public', cors(), (req, res) => {
res.json({ status: 'ok' });
});
// Private endpoint - restricted
const privateCors = cors({ origin: 'https://app.yourdomain.com' });
app.get('/api/private', privateCors, (req, res) => {
res.json({ secret: 'data' });
});
Fixing CORS Without the cors Package (Manual Headers)
If you don’t want an extra dependency, you can set headers manually:
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'https://app.yourdomain.com');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Credentials', 'true');
if (req.method === 'OPTIONS') {
return res.sendStatus(204);
}
next();
});
Still, we recommend the cors package because it correctly handles edge cases like the Vary header, which is critical when caching responses for multiple origins.
Common Mistakes That Keep Breaking CORS
- Putting CORS after authentication middleware. Preflight requests don’t include credentials, so your auth will reject them.
- Using
origin: '*'withcredentials: true. The browser will reject this combination. - Forgetting the protocol.
yourdomain.comis not a valid origin. It must behttps://yourdomain.com. - Trailing slashes.
https://site.com/andhttps://site.comare treated differently by some checks. - Assuming CORS is a server error. It’s a browser policy. Postman, curl, and server-to-server calls ignore CORS entirely.
- Deploying behind a reverse proxy (Nginx, Cloudflare) that strips headers. Check your proxy config too.

Debugging Checklist
- Open DevTools > Network tab and click the failing request.
- Check the Response Headers for
Access-Control-Allow-Origin. - Look for a preceding OPTIONS request and inspect its status. It should return
204or200. - Confirm the
Originheader sent by the browser matches what you allow on the server. - Test the endpoint with curl. If it works with curl but fails in the browser, it’s 100% a CORS config issue.
Complete Production-Ready Example
const express = require('express');
const cors = require('cors');
const app = express();
const allowedOrigins = [
'https://app.yourdomain.com',
'https://admin.yourdomain.com',
process.env.NODE_ENV === 'development' && 'http://localhost:3000'
].filter(Boolean);
const corsOptions = {
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Blocked by CORS policy'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
maxAge: 86400 // Cache preflight for 24h
};
app.use(cors(corsOptions));
app.options('*', cors(corsOptions));
app.use(express.json());
app.get('/api/health', (req, res) => res.json({ status: 'ok' }));
app.listen(5000, () => console.log('API running on port 5000'));
FAQ
Why does CORS only fail in the browser and not in Postman?
CORS is enforced by the browser, not the server. Postman, curl, and server-to-server requests don’t run the CORS check, so they get the response normally. The server always sends the data; the browser just refuses to hand it to your JavaScript.
Is CORS a security feature I can just disable?
You can’t disable it from the frontend. CORS protects your users’ browsers from malicious cross-origin requests. The correct approach is to configure your server to explicitly allow the origins that should have access.
Can I fix CORS from the frontend?
No. CORS headers must be set by the server. Frontend workarounds (like disabling browser security or using a proxy) are only useful for local development.
What is a preflight request?
Before certain requests (like PUT, DELETE, or requests with custom headers), the browser sends an OPTIONS request to check if the actual request is allowed. Your server must respond to this with the appropriate CORS headers and a 200 or 204 status.
Should I use origin: '*' in production?
Only for truly public APIs that don’t require authentication or cookies. For any authenticated endpoint, always specify exact origins.
Does the order of middleware matter?
Yes, absolutely. Always register cors() before authentication, body parsers that reject requests, or route handlers. Otherwise, preflight requests may fail before CORS headers are added.
Wrapping Up
CORS errors in Node.js feel scary, but they follow predictable rules. Install the cors middleware, configure your allowed origins explicitly, handle credentials correctly, and make sure CORS runs early in your middleware chain. With the snippets above, you should be able to fix any CORS error your Express app throws at you.
Need help architecting a secure Node.js API or dealing with more complex authentication scenarios? The team at Box Software builds and audits production-grade Node.js backends every day. Get in touch through our contact page.
