GuidesNODE.JS
Fix CORS errors in Express
Browser cross-origin errors happen when an API response omits headers that permit the requesting domain. When you control the Node.js backend, installing and configuring the express cors middleware resolves these errors by emitting the required headers. If you are instead calling a third-party API you cannot modify, you need a proxy rather than server-side middleware.
Confirm it is a CORS problem
Check the browser developer console for messages referencing a missing Access-Control-Allow-Origin header. Non-browser clients such as curl or Postman ignore CORS rules entirely, so a successful response outside the browser points at browser-side header validation. If the console text is ambiguous, walk through the diagnosis guide first.
Install the cors package
The cors package on npm is the standard Express middleware for CORS response headers. TypeScript projects also install the community type definitions from DefinitelyTyped as a development dependency, because the package ships none of its own.
$ npm install cors
# TypeScript projects: the package ships no type definitions
$ npm install --save-dev @types/corsEnable CORS for all routes
import express from 'express';
import cors from 'cors';
const app = express();
// Before routes, so every response carries the headers
app.use(cors());
app.get('/products/:id', (req, res) => {
res.json({ msg: 'Hello' });
});
app.listen(3000);Calling app.use(cors()) adds the header Access-Control-Allow-Origin: * to every outgoing response and answers preflight OPTIONS requests automatically.
Order matters in Express. Register the middleware before defining routes, and before any error handlers whose responses also need the headers.
Restrict origins in production
app.use(
cors({
origin: ['https://app.example.com', 'https://admin.example.com'],
}),
);
// Sends Access-Control-Allow-Origin: <matched origin> and Vary: Origin.
// A function form origin(origin, callback) can load the list dynamically.The default * allows any origin, and origin: true reflects whatever origin asks, so any website can read your API from a visitor's browser. For production APIs serving known frontends, list explicit domains: the origin option accepts a string, an array of strings or regular expressions, a RegExp, or a callback function with signature origin(origin, callback).
When origin is a specific string, the middleware also appends the Vary: Origin response header so shared caches do not serve one origin's cached response to another.
How preflight requests are handled
Browsers send an HTTP OPTIONS probe before non-simple requests, such as those with custom headers or methods like PUT, PATCH, and DELETE. When the middleware is registered application-wide with app.use(cors()), it answers these OPTIONS requests for all routes and responds with status 204 by default.
The methods option defaults to GET,HEAD,PUT,PATCH,POST,DELETE, and allowedHeaders defaults to reflecting whatever the request lists in Access-Control-Request-Headers. If you apply the middleware per route instead of globally, add app.options('*', cors()) before the other routes so preflights still get answered.
Sending cookies or Authorization
app.use(
cors({
origin: 'https://app.example.com',
credentials: true,
}),
);
// Browser side, cookies only flow when requested:
await fetch('https://api.example.com/account', { credentials: 'include' });
// axios: axios.get(url, { withCredentials: true })Setting credentials: true adds Access-Control-Allow-Credentials: true to the response. Browsers reject a credentialed response when Access-Control-Allow-Origin is *, so the server must echo an explicit origin string instead.
The browser must be told to send credentials as well. Pass credentials: 'include' to fetch, or set withCredentials: true in axios.
Verify the fix
$ curl -i -H "Origin: https://app.example.com" http://localhost:3000/products/1
HTTP/1.1 200 OK
access-control-allow-origin: https://app.example.com
vary: Origin
$ curl -i -X OPTIONS -H "Origin: https://app.example.com" -H "Access-Control-Request-Method: POST" http://localhost:3000/products
HTTP/1.1 204 No Content
access-control-allow-origin: https://app.example.com
access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETEInspect the response headers: Access-Control-Allow-Origin must match the origin your frontend sends, the OPTIONS probe must return a 204 listing the requested method in Access-Control-Allow-Methods, and credentialed setups must include Access-Control-Allow-Credentials: true. Paste the raw headers into the header checker to evaluate them against browser rules.
Good questions.
Does the cors middleware block requests on the server?
No. The middleware only sets response headers, and browsers enforce CORS by reading those headers. Non-browser clients such as curl, Postman, and other servers receive and process every request regardless of the configuration.
Where should app.use(cors()) sit in the middleware chain?
Register app.use(cors()) before any route definitions and before error-handling middleware. Handlers registered earlier run without the CORS headers, and error responses thrown before registration miss them as well.
How do I enable CORS for a single route in Express?
Pass the middleware to the route directly, for example app.get('/data', cors(), handler). Because preflight requests then have no matching handler, also register app.options('*', cors()) before the routes.
Why do some legacy browsers fail on the OPTIONS response?
Some legacy browsers, including IE11 and various SmartTVs, choke on the default 204 preflight status. Pass optionsSuccessStatus: 200 in the options object to answer with a plain 200 instead.