How to Add CORS Middleware to an Express API
Express APIs work fine in curl and Postman but block browser requests because browsers enforce CORS - they require Access-Control headers the server must send explicitly. Adding a single middleware function before your routes fixes all of it.
Why browsers block the request (and curl doesn't)
CORS is a browser construct, not an HTTP protocol rule. When a browser sees
a fetch to a different origin, it checks the response for an
Access-Control-Allow-Origin header before handing the data to your JavaScript.
curl and Postman skip that check entirely - they are not browsers.
So the API is not broken. It just has no CORS headers. Browsers refuse the response; curl succeeds. The fix is a middleware that attaches the headers to every response.
The middleware
Register it with app.use() before any route handler so every response gets the headers:
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
return res.status(204).end();
}
next();
});
Access-Control-Allow-Origin: *tells browsers any origin may read the response.Access-Control-Allow-Methodslists every method your API accepts.Access-Control-Allow-Headerslists the request headers callers are allowed to send.- The
OPTIONSshort-circuit returns204 No Contentimmediately - no body needed.
Why OPTIONS needs special handling
Before sending a POST, PUT, or any request with a custom header, the browser
sends an automatic preflight using the OPTIONS method. If your route handlers
don't define OPTIONS, Express returns 404 and the browser aborts before the real
request ever fires. The middleware above intercepts every OPTIONS call and returns
204 with the CORS headers, so the preflight succeeds.
Verify it:
# Preflight - should return 204 with Access-Control headers
curl -v -X OPTIONS http://localhost:3000/tasks
# Real request - headers should appear on the response
curl -sD - http://localhost:3000/tasks | grep -i access-control
When to tighten the wildcard
Access-Control-Allow-Origin: * is fine for public, read-only APIs. Two scenarios
require a specific origin instead of *:
- Cookie-authenticated requests -
credentials: 'include'requires the server to echo the exactOriginheader back, not*. - Private APIs - a wildcard lets any website call your API from a user's browser.
Production CORS middleware usually reads req.headers.origin, checks it against an
allowlist, and echoes it back if it matches:
const ALLOWED = new Set(['https://app.example.com', 'https://www.example.com']);
app.use((req, res, next) => {
const origin = req.headers.origin;
if (ALLOWED.has(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
}
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Vary', 'Origin'); // tell caches this header changes per origin
if (req.method === 'OPTIONS') return res.status(204).end();
next();
});
The Vary: Origin header tells CDNs and proxies not to serve one origin's cached
response to another.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Writing an Express app.use() middleware that sets Access-Control-* headers on every response
- Short-circuiting OPTIONS preflight requests with a 204 before route handlers run
- Tightening CORS from wildcard to an origin allowlist for cookie-authenticated APIs
FAQ
Why does my Express API work in curl but not in the browser?
CORS is a browser-only rule. Curl and Postman skip it. The browser checks for Access-Control-Allow-Origin on the response and blocks the data if it's missing. Adding CORS middleware to Express solves it - curl behavior is unchanged.
What is a CORS preflight request?
Before sending a non-simple request (POST with JSON, any request with an Authorization header, etc.), the browser sends an OPTIONS request to ask the server if that request is allowed. Your Express app needs to respond 204 with the Access-Control headers or the real request never fires.
Should I use the cors npm package or write it manually?
Either works. The cors package is convenient and well-tested. Writing it manually - res.setHeader + an OPTIONS check - is about 8 lines and teaches you exactly what headers do what. For production, the cors package handles edge cases like Vary and credential modes more robustly.
What is CORS middleware?
CORS middleware runs on each request to add the Access-Control-Allow-Origin and related headers so browsers allow cross-origin requests. In Express it can be the cors package or a few lines you write yourself.
What is Express middleware?
Express middleware is a function with access to the request, response, and next() that runs in order on every request - used for parsing, auth, logging, CORS, and error handling before the route handler.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →