On this page

Engineering6 min read

Fix Express CORS Error — No 'Access-Control-Allow-Origin' Header

Express CORS errors are blocked by the browser, not caused by a server crash. Here's why they happen, how to configure the cors package correctly for production, handle preflight requests, send credentials, and debug CORS in 60 seconds.

expresscorsnodejsjavascriptapi

Understanding What CORS Actually Is

CORS (Cross-Origin Resource Sharing) is enforced by the browser, not the server. Your API received the request fine. The browser saw the response didn't include Access-Control-Allow-Origin and blocked your JavaScript from reading it.

This means:

  • curl and Postman work fine — they don't enforce CORS
  • The error is not a server crash — it's a browser security policy
  • The fix is adding response headers to your Express server

The Fix: Use the cors Package

bash
npm install cors
javascript
const express = require('express')
const cors = require('cors')

const app = express()

// Allow all origins — use only in development
app.use(cors())

app.get('/api/users', (req, res) => {
  res.json({ users: [] })
})

app.listen(3001)

cors() with no arguments adds Access-Control-Allow-Origin: * to every response. Works in development. Too permissive for production.

Production Configuration

javascript
const corsOptions = {
  origin: process.env.ALLOWED_ORIGIN || 'https://yourdomain.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,  // required if sending cookies or Authorization headers
}

app.use(cors(corsOptions))

Warning: Never use origin: '*' with credentials: true. Browsers reject this combination. If you're sending cookies or auth headers, specify the exact origin.

Multiple Allowed Origins

javascript
const allowedOrigins = [
  'https://yourdomain.com',
  'https://www.yourdomain.com',
  'http://localhost:3000',  // dev only
]

app.use(cors({
  origin: (origin, callback) => {
    // Allow requests with no origin (curl, mobile apps)
    if (!origin) return callback(null, true)

    if (allowedOrigins.includes(origin)) {
      callback(null, true)
    } else {
      callback(new Error(`CORS: origin ${origin} not allowed`))
    }
  },
  credentials: true,
}))

Preflight Requests (OPTIONS)

Browsers send a preflight OPTIONS request before POST, PUT, DELETE, or requests with custom headers. Your server must respond to OPTIONS correctly or the actual request never fires.

The cors package handles this automatically if applied before your routes. If you're seeing the error only on non-GET requests, check middleware order:

javascript
// ✅ Correct order — cors before routes
app.use(cors(corsOptions))
app.use(express.json())
app.use('/api', router)

// ❌ Wrong order — cors after routes won't catch preflight
app.use('/api', router)
app.use(cors(corsOptions))

Or handle preflight explicitly:

javascript
app.options('*', cors(corsOptions))  // enable pre-flight for all routes
app.use(cors(corsOptions))

Sending Credentials (Cookies + Auth Headers)

If your frontend sends credentials: 'include' or an Authorization header:

javascript
// Frontend
fetch('https://api.yourdomain.com/users', {
  credentials: 'include',
  headers: {
    'Authorization': `Bearer ${token}`,
  },
})

Express backend must:

javascript
// ✅ Exact origin + credentials: true
app.use(cors({
  origin: 'https://yourdomain.com',  // exact origin, not '*'
  credentials: true,
}))

Note: If you see The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '*' when the request's credentials mode is 'include' — this is why. Switch from '*' to an exact origin.

Manual Header Approach (Without cors Package)

javascript
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'https://yourdomain.com')
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
  res.header('Access-Control-Allow-Credentials', 'true')

  if (req.method === 'OPTIONS') {
    return res.sendStatus(200)
  }

  next()
})

Diagnosing CORS Issues

bash
# curl doesn't enforce CORS — if this works, it's definitely a CORS config issue
curl -I http://localhost:3001/api/users

# Simulate preflight request
curl -X OPTIONS http://localhost:3001/api/users \
  -H "Origin: http://localhost:3000" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type" \
  -v

CORS Error Checklist

SymptomCheck
Works in Postman/curl, fails in browserClassic CORS — add headers
Works for GET, fails for POSTPreflight OPTIONS not handled
credentials: true not workingUsing origin: '*' — must use exact origin
Error on Authorization headerAdd Authorization to allowedHeaders
Works locally, fails in productionALLOWED_ORIGIN env var not set for prod domain

For CORS errors in complex setups — nginx proxy in front of Express, multiple API subdomains, custom auth middleware that runs before CORS — paste your Express config and the full error into DebugAI. It identifies where in the middleware chain the headers are getting dropped.

Debug faster starting today.

Free VS Code extension. 10 sessions/day. No credit card.

Install Free →

Related Posts

Engineering

Fix Django IntegrityError — UNIQUE, NOT NULL, and Foreign Key Violations

5 min read

Engineering

Fix FastAPI 422 Unprocessable Entity — 5 Causes and Fixes

5 min read

← All posts