NodeJSCORS

CORS

CORS — Cross-Origin Resource Sharing — is the browser mechanism that decides whether JavaScript on one origin may read responses from another. If your API at api.example.com is called by a frontend at app.example.com, the browser enforces CORS, and without the right response headers the call fails. The cors middleware sets those headers for you. The key to using it well is understanding what problem it solves — and what it does not.

The same-origin policy, and what "origin" means

An origin is the triple scheme + host + port. Two URLs share an origin only if all three match. By default browsers block cross-origin reads from JavaScript — CORS is how a server opts in to being read by specific origins.

URL pair

Same origin?

https://site.com vs https://site.com/x

Yes (path is irrelevant)

https://site.com vs http://site.com

No (scheme differs)

https://site.com vs https://api.site.com

No (host differs)

https://site.com vs https://site.com:8443

No (port differs)

CORS is enforced by the browser, not your server
CORS rules live in the **browser**. Your server merely sends headers (`Access-Control-Allow-Origin`, etc.) that tell the browser what's permitted; the browser then allows or blocks the JS from *reading* the response. Tools like `curl`, Postman, and server-to-server calls ignore CORS entirely — so a "CORS error" is always a browser-side symptom of missing/incorrect server headers.
Basic setup with the `cors` package

JS
import cors from 'cors'

// Allow ALL origins — fine for a fully public, read-only API:
app.use(cors())

// Restrict to one trusted frontend (the common production case):
app.use(cors({ origin: 'https://app.example.com' }))
`cors()` with no options allows EVERY origin — rarely what you want
Bare `app.use(cors())` sets `Access-Control-Allow-Origin: *`, letting any website's JavaScript call your API. That's acceptable only for genuinely public, non-credentialed data. For anything user-specific or authenticated, **whitelist** the exact origins you trust. Reflexively allowing `*` is a frequent security misstep.
Whitelisting multiple origins

JS
const allowed = new Set([
  'https://app.example.com',
  'https://admin.example.com',
  'http://localhost:5173',          // dev frontend
])

app.use(cors({
  origin(origin, cb) {
    // Allow same-origin / non-browser requests (no Origin header):
    if (!origin || allowed.has(origin)) return cb(null, true)
    cb(new Error('Not allowed by CORS'))
  },
}))
The function form echoes back the matched origin
When you allow multiple origins, you can't just send a static header — the spec disallows listing several in `Access-Control-Allow-Origin`. The function form checks the incoming `Origin` against your whitelist and, if allowed, the middleware reflects *that one* origin back. Requests with no `Origin` (same-origin navigations, curl) are typically allowed through.
Preflight requests (OPTIONS)

For "non-simple" requests — custom headers, methods like PUT/DELETE, or JSON content type — the browser first sends an automatic OPTIONS preflight asking permission. The cors middleware answers it automatically when mounted with app.use:

Text
Browser preflight:
  OPTIONS /api/users
  Origin: https://app.example.com
  Access-Control-Request-Method: PUT
  Access-Control-Request-Headers: content-type, authorization

Server (cors middleware) replies:
  Access-Control-Allow-Origin: https://app.example.com
  Access-Control-Allow-Methods: GET,POST,PUT,DELETE
  Access-Control-Allow-Headers: content-type, authorization
  Access-Control-Max-Age: 86400   ← cache this preflight for a day
Mount `cors` BEFORE your routes, or preflights 404
The preflight `OPTIONS` must be answered before your route logic runs. Register `app.use(cors())` *above* your routes so the middleware can intercept and respond to `OPTIONS` — otherwise the preflight falls through to a 404 and the browser blocks the real request. If you need per-route control, also add `app.options('/path', cors(...))`.
Credentials — cookies and auth headers

JS
app.use(cors({
  origin: 'https://app.example.com',   // MUST be specific, not '*'
  credentials: true,                   // allow cookies / Authorization
}))

// Browser side: fetch('https://api...', { credentials: 'include' })
`credentials: true` is incompatible with `origin: '*'`
To send cookies or `Authorization` headers cross-origin, the server must set `Access-Control-Allow-Credentials: true` **and** echo a *specific* origin — the wildcard `*` is forbidden with credentials, and the browser will reject the response. The client must also opt in with `credentials: 'include'`. Allowing credentials from a broad/dynamic origin set is dangerous: it can expose authenticated data to malicious sites. Whitelist tightly.
Common options

Option

Controls

origin

Which origins are allowed (string, array, regex, or function)

methods

Allowed HTTP methods (default GET,HEAD,PUT,PATCH,POST,DELETE)

allowedHeaders

Request headers the client may send

exposedHeaders

Response headers JS is allowed to read

credentials

Allow cookies / auth (true/false)

maxAge

Seconds the browser may cache the preflight

Per-route CORS

JS
// Public endpoint: open to all. Private endpoints: locked down.
app.get('/api/public', cors(), publicHandler)

const strict = cors({ origin: 'https://app.example.com', credentials: true })
app.use('/api/account', strict, accountRouter)
CORS is not a substitute for authentication
Loosening CORS does not authorize anyone — it only lets a browser *read* a response. Non-browser clients ignore it entirely. CORS protects your *users* (stopping a malicious site from reading their authenticated data in their browser); it does **not** protect your *API* from direct abuse. You still need real [auth](/nodejs/error-middleware), rate limiting, and validation.
Next
Read and write cookies cleanly with middleware: [Cookies (cookie-parser)](/nodejs/cookies-middleware).