Express makes CORS relatively painless, but there are a few gotchas that catch people off guard. Let me walk through every setup I’ve seen work in production.

The cors Package (Easiest Option)

npm install cors

The One-Liner (Development Only)

const cors = require('cors');
app.use(cors());
```text

This allows all origins, all methods, all headers. Fine for local development. Do NOT use this in production.

### Allow a Single Origin

```javascript
const cors = require('cors');

app.use(cors({
  origin: 'https://myapp.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400,
}));

Allow Multiple Origins

This is where it gets slightly tricky. The cors package doesn’t accept an array for origin — it accepts a function:

const cors = require('cors');

const allowedOrigins = [
  'https://myapp.com',
  'https://admin.myapp.com',
  'https://staging.myapp.com',
];

const corsOptions = {
  origin: function (origin, callback) {
    // Allow requests with no origin (like mobile apps, curl, Postman)
    if (!origin) return callback(null, true);
    
    if (allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true,
};

app.use(cors(corsOptions));
```text

The `!origin` check is important. Some clients (mobile apps, server-to-server calls) don't send an `Origin` header. Without this check, those requests would fail.

### Dynamic Origin (Regex)

If you have many subdomains and don't want to list them all:

```javascript
const corsOptions = {
  origin: function (origin, callback) {
    if (!origin) return callback(null, true);
    
    if (/\.myapp\.com$/.test(origin) || origin === 'https://myapp.com') {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true,
};

Per-Route CORS

Sometimes you want different CORS rules for different endpoints:

// Public API — any origin
app.get('/api/public', cors(), (req, res) => {
  res.json({ message: 'This is public' });
});

// Authenticated API — specific origin + credentials
app.get('/api/private', cors({
  origin: 'https://myapp.com',
  credentials: true,
}), (req, res) => {
  res.json({ message: 'This is private' });
});

// Webhook endpoint — no CORS needed
app.post('/api/webhooks/stripe', (req, res) => {
  // No CORS config — this is server-to-server
  res.sendStatus(200);
});
```text

### Enable CORS for Specific Routes Only

If you only want CORS on your API routes:

```javascript
app.use('/api', cors({
  origin: 'https://myapp.com',
  credentials: true,
}));

// These have CORS:
app.get('/api/users', ...);
app.post('/api/auth/login', ...);

// This does NOT have CORS:
app.get('/', ...);

Manual CORS (Without the Package)

If you don’t want another dependency, it’s not hard to do manually:

const allowedOrigins = ['https://myapp.com', 'https://admin.myapp.com'];

app.use((req, res, next) => {
  const origin = req.headers.origin;
  
  if (origin && allowedOrigins.includes(origin)) {
    res.header('Access-Control-Allow-Origin', origin);
    res.header('Access-Control-Allow-Credentials', 'true');
    res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    res.header('Access-Control-Expose-Headers', 'X-Total-Count, X-Page');
    res.header('Access-Control-Max-Age', '86400');
  }
  
  // Handle preflight
  if (req.method === 'OPTIONS') {
    return res.sendStatus(204);
  }
  
  next();
});
```text

This does the same thing as the cors package. Use whichever approach you prefer.

## CORS with Apollo GraphQL

```javascript
const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@apollo/server/express4');
const cors = require('cors');

const corsOptions = {
  origin: 'https://myapp.com',
  credentials: true,
};

app.use('/graphql', cors(corsOptions), express.json());
app.use('/graphql', expressMiddleware(server));

CORS with Socket.IO

WebSockets have their own CORS handling. Socket.IO handles it through the cors option:

const { Server } = require('socket.io');

const io = new Server(httpServer, {
  cors: {
    origin: 'https://myapp.com',
    methods: ['GET', 'POST'],
    credentials: true,
  },
});
```text

## Common Mistakes

**1. Using cors() globally in production.** This allows any website to call your API. If your API returns user data, any malicious site could access it (from the user's browser, with their cookies).

**2. Forgetting the OPTIONS handler.** If you're doing manual CORS, you MUST handle OPTIONS requests. Without it, preflight requests return a 404 or whatever your default handler does.

**3. Not setting Expose-Headers.** Your API returns custom headers like `X-Total-Count` for pagination, but your frontend can't read them. Add `Access-Control-Expose-Headers`.

**4. CORS + HTTPS mismatch.** Your frontend is on `https://myapp.com` but the origin in your CORS config is `http://myapp.com`. These are different origins. Match the protocol.

**5. Putting CORS on the client.** I've seen developers add CORS headers to their fetch calls. It doesn't work. CORS headers go on the SERVER response, not the client request.

## Verify Your Setup