troubleshooting2025-05-28·8·49/348

Express.js CORS 에러 해결 방법

A complete guide to understanding and fixing Cross-Origin Resource Sharing errors in Express.js applications.

Express.js CORS 에러 해결 방법

Introduction

CORS (Cross-Origin Resource Sharing) errors are the number one headache when developing full-stack applications with a separate frontend and backend. After building numerous APIs in Lisbon, I have compiled this definitive guide to understanding and fixing CORS issues in Express.js.

Environment

node --version
# v20.11.0

npm list express cors
# my-app@1.0.0
# +-- cors@2.8.5
# +-- express@4.18.2

Problem

When your frontend at http://localhost:3000 makes a request to your backend at http://localhost:5000, the browser blocks the response and you see this error:

Access to XMLHttpRequest at 'http://localhost:5000/api/users' from origin
'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin'
header is present on the requested resource.

Or for preflight requests:

Access to XMLHttpRequest at 'http://localhost:5000/api/users' from origin
'http://localhost:3000' has been blocked by CORS policy: Response to preflight request
doesn't pass access control check: It does not have HTTP ok status.

Analysis

CORS is a security mechanism enforced by browsers. The server must explicitly allow cross-origin requests by sending the correct HTTP headers. Here is the flow:

Browser: OPTIONS /api/users (preflight)
Server: Must respond with Access-Control-Allow-Origin header

Browser: GET /api/users (actual request)
Server: Must include CORS headers in response

The common CORS headers are:

Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400

Solution

Solution 1: Use the cors middleware (recommended)

const express = require('express');
const cors = require('cors');

const app = express();

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

app.listen(5000);

Solution 2: Configure specific origins

const corsOptions = {
  origin: 'http://localhost:3000',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400
};

app.use(cors(corsOptions));

Solution 3: Dynamic origin validation

const allowedOrigins = [
  'http://localhost:3000',
  'https://myapp.com',
  'https://www.myapp.com'
];

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

app.use(cors(corsOptions));

Solution 4: Manual CORS headers (without middleware)

app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  
  if (req.method === 'OPTIONS') {
    return res.sendStatus(200);
  }
  
  next();
});

Solution 5: Environment-based configuration

// cors.config.js
const whitelist = {
  development: ['http://localhost:3000', 'http://localhost:5173'],
  production: ['https://myapp.com']
};

const corsOptions = {
  origin: function (origin, callback) {
    const allowed = whitelist[process.env.NODE_ENV] || [];
    if (!origin || allowed.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('CORS not allowed'));
    }
  },
  credentials: true
};

module.exports = corsOptions;

// server.js
const cors = require('cors');
const corsOptions = require('./cors.config');

app.use(cors(corsOptions));

Lessons Learned

  1. Always handle OPTIONS preflight requests - The browser sends these automatically
  2. Never use Access-Control-Allow-Origin: * with credentials - This is forbidden by the spec
  3. Set Access-Control-Max-Age to reduce preflight request overhead
  4. Test with curl first to verify headers are present before blaming CORS
  5. Use environment variables for CORS origins in production deployments

This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.