/commands/cors/enable-cors-express
Enable CORS in Express with the cors middleware
- tool
- CORS
- versions
- cors 2.8, Express 4 / 5
- os
- linux, macos, windows
Install the middleware with npm install cors and mount it before your routes: app.use(cors()) allows every origin with Access-Control-Allow-Origin: *, and app.use(cors({ origin: [...] })) allows only the listed origins, reflecting the caller's origin and adding Vary: Origin. The middleware also answers preflight OPTIONS requests with 204 No Content and the allowed methods and headers, so you do not write those handlers yourself.
Setup
npm init -y >/dev/null
npm install --silent --no-audit --no-fund express@5 cors@2 >/dev/null 2>&1
cat > app.js <<'EOF'
const express = require("express");
const cors = require("cors");
const app = express();
// Public endpoint: any origin, no credentials
app.get("/public", cors(), (req, res) => res.json({ ok: true }));
// API: two origins, cookies allowed, preflight cached 10 minutes
app.use("/api", cors({
origin: ["https://app.example.com", "https://admin.example.com"],
methods: ["GET", "POST", "PUT"],
allowedHeaders: ["Content-Type", "Authorization"],
credentials: true,
maxAge: 600,
}));
app.put("/api/items", (req, res) => res.json({ saved: true }));
const server = app.listen(5209, "127.0.0.1");
setTimeout(() => server.close(() => process.exit(0)), 8000);
EOF
node app.js &
sleep 2
show() { tr -d '\r' | grep -iE '^(HTTP|access-control|vary)'; }
echo "== GET /public"
curl -s -i http://127.0.0.1:5209/public -H "Origin: https://any.example" | show
echo "== preflight from an allowed origin"
curl -s -i -X OPTIONS http://127.0.0.1:5209/api/items -H "Origin: https://admin.example.com" \
-H "Access-Control-Request-Method: PUT" -H "Access-Control-Request-Headers: content-type" | show
echo "== preflight from another origin"
curl -s -i -X OPTIONS http://127.0.0.1:5209/api/items -H "Origin: https://evil.example" \
-H "Access-Control-Request-Method: PUT" | show
waitOutput
== GET /public HTTP/1.1 200 OK Access-Control-Allow-Origin: * == preflight from an allowed origin HTTP/1.1 204 No Content Access-Control-Allow-Origin: https://admin.example.com Vary: Origin Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: GET,POST,PUT Access-Control-Allow-Headers: Content-Type,Authorization Access-Control-Max-Age: 600 == preflight from another origin HTTP/1.1 204 No Content Vary: Origin Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: GET,POST,PUT Access-Control-Allow-Headers: Content-Type,Authorization Access-Control-Max-Age: 600
The preflight from an origin that is not on the list still gets 204, but without Access-Control-Allow-Origin. That missing header is what makes the browser block the request with No 'Access-Control-Allow-Origin' header is present.
Options
| Option | Default | Effect |
|---|---|---|
| origin | '*' | true reflects any origin; a string, RegExp or array allows matching origins; a function (origin, cb) decides per request |
| methods | GET,HEAD,PUT,PATCH,POST,DELETE | Access-Control-Allow-Methods in preflight responses |
| allowedHeaders | the request headers | Access-Control-Allow-Headers; when unset, the middleware echoes Access-Control-Request-Headers |
| exposedHeaders | none | Response headers JavaScript may read (Access-Control-Expose-Headers) |
| credentials | false | Adds Access-Control-Allow-Credentials: true for cookies and HTTP auth |
| maxAge | none | Access-Control-Max-Age in seconds: how long the browser caches the preflight |
| optionsSuccessStatus | 204 | Status of the preflight response; 200 for very old clients |
Pitfalls
- Order matters:
app.use(cors(...))must come before the routes and before anything that ends the response, such as an auth middleware that returns 401. Otherwise error responses lack the CORS headers and the browser reports a CORS error instead of the real status (see preflight does not have HTTP ok status). origin: '*'pluscredentials: trueproduces a response browsers reject; see wildcard origin with credentials.- Behind nginx or another proxy, make sure the proxy does not add its own
Access-Control-Allow-Originas well: two values in that header are also rejected. - CORS is enforced by browsers only. curl and server-to-server calls ignore it, so it is not an access control mechanism.
FAQ
Do I need app.options() for preflight requests?
Not when cors() is mounted with app.use(): the middleware answers OPTIONS preflights itself with 204 and does not call the next handler (preflightContinue is false by default). Route-level cors() on app.get() alone does not see OPTIONS, so mount it with app.use() or add an OPTIONS route.
Why is Access-Control-Allow-Origin missing for some origins?
With an origin list, the middleware adds the header only when the request's Origin is in the list. Other origins get a response without it, which the browser blocks. Check the exact spelling, scheme and port of the origin.
Can I use origin: '*' with credentials: true?
No. Browsers reject a wildcard Allow-Origin on credentialed requests. Give an explicit list or a function, so the middleware reflects the caller's origin.