Skip to content

CORS in Node.js

CORS errors are one of the most common points of confusion for developers building APIs. This lesson explains exactly what CORS is, who enforces it, and how to configure it correctly in Express.

What Is CORS, Really?

CORS (Cross-Origin Resource Sharing) is a browser security mechanism, not a server-side restriction. By default, browsers block JavaScript running on one origin (e.g. https://app.example.com) from reading responses from a different origin (e.g. https://api.example.com) unless the server explicitly allows it via response headers.

This means CORS never blocks the actual request from reaching your server, it only prevents the browser's JavaScript from reading the response, and it only applies to browsers. Non-browser clients (mobile apps, curl, server-to-server calls) are completely unaffected by CORS.

import cors from 'cors';
import express from 'express';

const app = express();

app.use(cors({
  origin: ['https://app.example.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  credentials: true,
}));

This configuration only allows browser requests originating from https://app.example.com, requests from other origins will have their response blocked by the browser (though the server still processes them).

Key CORS Response Headers

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Credentials: true
  • Access-Control-Allow-Origin tells the browser which origin(s) may read the response.
  • Using * allows any origin, but is incompatible with credentials: true.
  • Access-Control-Allow-Credentials must be set to allow cookies to be sent cross-origin.
  • Complex requests (custom headers, non-simple methods) trigger a browser "preflight" OPTIONS request first.

CORS Cheatsheet

Common CORS configurations for different scenarios.

Scenario Configuration
Public API, no cookies cors({ origin: '*' })
Specific frontend origin cors({ origin: 'https://app.example.com' })
Multiple allowed origins cors({ origin: ['https://a.com', 'https://b.com'] })
Cookies/credentials needed cors({ origin: '...', credentials: true })
Allow specific methods cors({ methods: ['GET', 'POST'] })

Understanding Preflight Requests

For requests beyond simple GET/POST with basic headers, browsers first send an automatic OPTIONS "preflight" request, asking the server whether the actual request would be allowed. The cors middleware responds to these automatically once configured, you rarely need to handle OPTIONS manually.

  • Preflight requests happen automatically for methods like PUT/DELETE or custom headers.
  • The cors middleware intercepts and responds to OPTIONS requests without needing a dedicated route.
  • A failed preflight means the browser never even sends the real request.

CORS Is Not a Substitute for Authentication

Because CORS only affects browsers reading responses, a non-browser client can always send a request to your API regardless of your CORS configuration. CORS controls which websites can use your API from client-side JavaScript, it says nothing about who is allowed to call your API at all, that's the job of authentication and authorization.

Common Mistakes

  • Assuming a CORS error means the request never reached the server, in most cases, it did, only the response was blocked from being read.
  • Setting origin: '*' together with credentials: true, an invalid, browser-rejected combination.
  • Treating permissive CORS settings as a security control, non-browser clients bypass CORS entirely.
  • Forgetting to include custom headers your frontend sends in the allowed CORS configuration.

Key Takeaways

  • CORS is a browser-enforced mechanism controlling which origins may read cross-origin responses.
  • It does not block the request from reaching the server, and doesn't apply to non-browser clients at all.
  • The cors middleware handles response headers and automatic preflight OPTIONS requests.
  • CORS is not an access-control or authentication mechanism, use real auth for that.

Pro Tip

When debugging a CORS error, check your server logs first, if the request actually arrived at your server, the issue is purely about response headers (fixable in your cors() config), not about the request being blocked outright.