DEV Community

Cover image for # What Is CORS and a Preflight Request? Explained for Developers
sudip khatiwada
sudip khatiwada

Posted on

# What Is CORS and a Preflight Request? Explained for Developers

Ever deployed your Node.js API only to see Access to fetch has been blocked by CORS policy in the browser console? You're not alone. CORS errors are among the most common frustrations for backend developers, yet the underlying mechanism is often misunderstood. Let's fix that.

The Foundation: Same-Origin Policy

Browsers enforce a security mechanism called the Same-Origin Policy (SOP). It prevents JavaScript running on https://myapp.com from making requests to https://api.example.com unless explicitly allowed.

Origin = Protocol + Domain + Port

  • https://api.example.com:443 → Origin A
  • https://api.example.com:3000 → Origin B (different port)
  • http://api.example.com:443 → Origin C (different protocol)

Without SOP, malicious scripts on evil.com could steal data from your bank's API while you're logged in. SOP blocks this by default.

What Is CORS?

Cross-Origin Resource Sharing (CORS) is the protocol that allows servers to relax SOP restrictions. It's a set of HTTP headers that tells browsers: "Yes, I trust requests from this external origin."

Critical insight: CORS is enforced by browsers, not servers. Your Node.js API receives and processes the request regardless. The browser decides whether to expose the response to your frontend JavaScript based on CORS headers.

How CORS Works (Simple Requests)

For basic GET or POST requests with standard headers, the browser:

  1. Sends the request with an Origin header
  2. Server responds with Access-Control-Allow-Origin: * (or specific origin)
  3. Browser checks the header and either allows or blocks JavaScript access to the response
import express from 'express';
const app = express();

app.get('/api/data', (req, res) => {
  res.setHeader('Access-Control-Allow-Origin', 'https://myapp.com');
  res.json({ message: 'CORS enabled for myapp.com' });
});

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

What Is a Preflight Request?

For non-simple requests, browsers send an OPTIONS request before the actual request. This is the preflight.

When Does a Preflight Trigger?

A preflight occurs when your request includes:

  • Custom headers (e.g., Authorization, X-API-Key)
  • HTTP methods other than GET, HEAD, or POST
  • Content-Type other than application/x-www-form-urlencoded, multipart/form-data, or text/plain

Example: A PUT request with Content-Type: application/json triggers a preflight.

The Preflight Flow

  1. Browser sends OPTIONS request:
   OPTIONS /api/users/123
   Origin: https://myapp.com
   Access-Control-Request-Method: PUT
   Access-Control-Request-Headers: Content-Type
Enter fullscreen mode Exit fullscreen mode
  1. Server responds with permissions:
   Access-Control-Allow-Origin: https://myapp.com
   Access-Control-Allow-Methods: PUT, DELETE
   Access-Control-Allow-Headers: Content-Type
   Access-Control-Max-Age: 86400
Enter fullscreen mode Exit fullscreen mode
  1. If approved, browser sends actual PUT request

Handling Preflight in Express.js

import express from 'express';
const app = express();

// Handle preflight for all routes
app.options('*', (req, res) => {
  res.setHeader('Access-Control-Allow-Origin', 'https://myapp.com');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.setHeader('Access-Control-Max-Age', '86400'); // Cache preflight for 24 hours
  res.sendStatus(204);
});

// Actual endpoint
app.put('/api/users/:id', (req, res) => {
  res.setHeader('Access-Control-Allow-Origin', 'https://myapp.com');
  res.json({ updated: true });
});

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

Pro tip: Use the cors npm package in production to avoid repetitive header management:

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

const app = express();

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

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

Common CORS Mistakes

  1. Setting Access-Control-Allow-Origin: * with credentials → Browsers reject this. Use specific origins when sending cookies or auth tokens.

  2. Forgetting to handle OPTIONS → Preflight requests fail silently, and developers blame CORS instead of missing OPTIONS handlers.

  3. Server-side CORS logic → Remember: CORS headers tell the browser what to allow. Your server already processed the request.

Key Takeaways

  • Same-Origin Policy protects users; CORS allows controlled exceptions
  • Browsers enforce CORS, not servers
  • Preflight requests (OPTIONS) occur for non-simple requests to verify permissions before the actual request
  • Always handle OPTIONS explicitly in production APIs
  • Use the cors package to simplify header management in Node.js

CORS errors feel frustrating because they happen after your server processed the request. Understanding the browser–server handshake transforms debugging from guesswork into systematic problem-solving.

What CORS challenge have you faced in production? Share your experience below!


Top comments (0)