Tutorial

How to enable CORS in Node.js and Express

Your Express API returns data fine from curl, but the browser blocks it with "No Access-Control-Allow-Origin header is present". Here's how to fix it, ordered from "fastest" to "production-ready".

~6 min read · Node.js · Express

The one-line fix (development only)

Install the cors middleware and use it with default settings — it adds Access-Control-Allow-Origin: * to every response:

npm install cors
import express from 'express';
import cors from 'cors';

const app = express();
app.use(cors());  // Allows any origin — DEV ONLY
app.get('/api/users', (req, res) => res.json([{ id: 1, name: 'A' }]));
app.listen(3000);

Three caveats before you ship this to production:

The right way: origin allowlist

For production, reflect a specific origin from a known allowlist. The cors middleware accepts a function:

const ALLOWED_ORIGINS = new Set([
  'https://app.example.com',
  'https://admin.example.com',
  'http://localhost:3000'  // dev only
]);

app.use(cors({
  origin: (origin, callback) => {
    // Same-origin / curl / mobile requests have no Origin header.
    if (!origin) return callback(null, true);
    if (ALLOWED_ORIGINS.has(origin)) return callback(null, origin);
    return callback(new Error(`Origin ${origin} not allowed`));
  },
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-Id'],
  exposedHeaders: ['X-RateLimit-Remaining'],
  maxAge: 600,            // browser caches preflight for 10 min
  credentials: true       // allow cookies / auth headers
}));

What each option does:

Without the cors package

If you don't want a dependency, the same behavior is ~10 lines:

const ALLOWED = new Set([
  'https://app.example.com',
  'http://localhost:3000'
]);

app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (origin && ALLOWED.has(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Vary', 'Origin');
    res.setHeader('Access-Control-Allow-Credentials', 'true');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    res.setHeader('Access-Control-Max-Age', '600');
  }
  // Short-circuit OPTIONS preflight before route handlers see it.
  if (req.method === 'OPTIONS') return res.sendStatus(204);
  next();
});

Don't forget Vary: Origin — without it, CDNs and browser caches will serve the wrong Access-Control-Allow-Origin to a different origin and you'll get silent intermittent CORS errors.

Per-route configuration

Most APIs want different rules per route — a public health endpoint can be *, but the auth endpoints need a strict allowlist:

const publicCors  = cors();
const strictCors  = cors({
  origin: ['https://app.example.com'],
  credentials: true
});

app.get('/health', publicCors, (req, res) => res.send('ok'));
app.use('/api/auth', strictCors);
app.use('/api/data', strictCors);

Common gotchas

When you don't control the upstream

If the API you're hitting isn't yours and won't add CORS headers, run it through a CORS proxy. The proxy sends the headers the browser needs. We open-source ours for self-host, and run a managed version at api.corsproxy.dev — free tier is 100 requests/day.

Skip the setup

Need to proxy a third-party API instead of configuring CORS yourself? Get a free key in 30 seconds.