The Complete Guide to CORS for Modern Frontend Developers

Posted by

Stop fighting mysterious CORS errors, learn how Cross-Origin Resource Sharing actually works, why browsers enforce it, and how to fix it properly.

Stop fighting mysterious CORS errors, learn how Cross-Origin Resource Sharing actually works, why browsers enforce it, and how to fix it properly.

Introduction

You write a simple fetch call:

fetch("https://api.example.com/data");

And then this happens ๐Ÿ‘‡

Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000' 
has been blocked by CORS policy.

You Google it.
You find random Stack Overflow answers like โ€œjust use mode: 'no-cors'โ€ or โ€œinstall a Chrome plugin.โ€
It worksโ€ฆ kind of.

But you still donโ€™t know why it happens or how to fix it properly.

This guide will finally make CORS make sense step by step, from browser rules to backend fixes in plain English.


1๏ธ. The Core Concept: What Even Is CORS?

CORS stands for Cross-Origin Resource Sharing.

To understand it, first you need to know what a โ€œsame originโ€ is.

An origin is defined by 3 parts:

protocol + domain + port

So these two are the same origin ๐Ÿ‘‡
โœ… https://myapp.com โ†’ https://myapp.com/api

But these are different origins ๐Ÿ‘‡
โŒ https://myapp.com โ†’ https://api.myapp.com
โŒ http://localhost:3000 โ†’ https://api.myapp.com

By default, the Same-Origin Policy (SOP) blocks cross-origin requests made by browsers.

Thatโ€™s why your app canโ€™t just talk to any random API; itโ€™s a security feature, not a bug.

CORS is the browserโ€™s permission mechanism for safely relaxing the Same-Origin Policy.


2๏ธ. Why the Browser Blocks You (Not Postman)

Hereโ€™s whatโ€™s confusing:
 Your API call works fine in Postman, but fails in Chrome.

Thatโ€™s because Postman doesnโ€™t have a Same-Origin Policy; itโ€™s not a browser.

Only browsers enforce CORS to prevent malicious websites from making requests on behalf of logged-in users.

Think of it as a security bouncer at the door:

โ€œYouโ€™re not from this domain? Show me permission before I let you in.โ€


3๏ธ. How CORS Actually Works (Step by Step)

Every cross-origin request includes an Origin header.

Example:

Origin: http://localhost:3000

Your backend must explicitly say it allows that origin:

Access-Control-Allow-Origin: http://localhost:3000

If the response doesnโ€™t include this header, the browser blocks the response
even if the server sent a valid 200 OK.

โœ… Server responds with header โ†’ Browser allows response
โŒ No header โ†’ Browser blocks response


4๏ธ. Types of CORS Requests

CORS requests come in two main types:

1. Simple Requests

Triggered by โ€œsafeโ€ methods like GET, POST, or HEAD, and only certain headers.

Example:

fetch("https://api.example.com/data", {
method: "GET",
});

Browser sends:

Origin: http://localhost:3000

Server must respond with:

Access-Control-Allow-Origin: http://localhost:3000

If it matches โ†’ โœ… success.
 If not โ†’ โŒ CORS error.


2. Preflight Requests

When you use custom headers or unsafe methods (PUT, PATCH, DELETE), the browser first sends a preflight request using the OPTIONS method.

Example:

OPTIONS /api/data
Origin: http://localhost:3000
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization

The backend must respond with:

Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization

Only then will the browser send your actual request.

If the preflight check fails, the browser cancels your real request automatically.


5๏ธ. The Key CORS Headers You Must Know


6๏ธ. Common Reasons Your CORS Request Fails

โŒ 1. Missing Access-Control-Allow-Origin

If your backend doesnโ€™t return that header โ†’ blocked.

Fix:

res.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");

Or for testing only (โš ๏ธ not production):

res.setHeader("Access-Control-Allow-Origin", "*");

โŒ 2. Credentials Without Proper Setup

When sending cookies or tokens:

Frontend:

fetch("/api/data", { credentials: "include" });

Backend:

Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Credentials: true

โš ๏ธ You canโ€™t use * when credentials: true is set.


โŒ 3. Missing Allowed Headers

If you send custom headers (like Authorization), your server must whitelist them:

Access-Control-Allow-Headers: Content-Type, Authorization

Otherwise, preflight fails silently.


โŒ 4. Wrong HTTP Methods

If your request uses PUT But your server only allows GET, POST The preflight will fail.

Fix:

Access-Control-Allow-Methods: GET, POST, PUT

โŒ 5. Misconfigured Proxies

If your dev proxy (e.g., Vite, Webpack, Next.js) doesnโ€™t forward the Origin header or response headers correctly,
Youโ€™ll see false CORS errors even if the backend is fine.

โœ… Always set changeOrigin: true or proxy: { '/api': { target, changeOrigin: true } }.


7๏ธ. Real-World Backend Fixes

Node.js / Express

import express from "express";
import cors from "cors";

const app = express();

app.use(
cors({
origin: "http://localhost:3000",
credentials: true,
})
);

app.get("/api/data", (req, res) => {
res.json({ message: "CORS works!" });
});

NestJS

app.enableCors({
origin: "http://localhost:3000",
credentials: true,
allowedHeaders: "Content-Type, Authorization",
methods: "GET,POST,PUT,DELETE",
});

Django

CORS_ALLOWED_ORIGINS = ["http://localhost:3000"]
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_HEADERS = ["content-type", "authorization"]

Spring Boot

@CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true")
@GetMapping("/api/data")
public ResponseEntity<String> getData() {
return ResponseEntity.ok("CORS Configured!");
}

โœ… Always configure on the server, not the browser.


8๏ธ. Why mode: 'no-cors' Itโ€™s not a Fix

Some devs try this:

fetch("/api/data", { mode: "no-cors" });

But it doesnโ€™t fix anything.
It just suppresses errors and returns an opaque response; you canโ€™t access data, status, or headers.

no-cors is a workaround for sending not receiving data. Donโ€™t use it for APIs that expect readable responses.


9๏ธ. Quick Visual Summary

Frontend (http://localhost:3000)
|
| OPTIONS /api/data (preflight)
| Origin: http://localhost:3000
v
Backend (https://api.example.com)
|
| Access-Control-Allow-Origin: http://localhost:3000
| Access-Control-Allow-Methods: GET,POST,PUT
|
v
Browser โœ… โ€œOkay, youโ€™re allowed.โ€

10. Debugging CORS Like a Pro

โœ… Check the Network tab for the OPTIONS preflight.
โœ… Compare your request Origin with the serverโ€™s allowed one.
โœ… Make sure all headers/methods match.
โœ… Donโ€™t test CORS using Postman, it skips CORS rules entirely.


Why CORS Exists (and Why Itโ€™s a Good Thing)

Without CORS, any random website could:

  • Read your banking data
  • Make hidden requests using your cookies
  • Exfiltrate sensitive information

CORS isnโ€™t there to make your life harder; itโ€™s there to keep users safe.
Once you understand it, it stops being frustrating and becomes logical.


Conclusion

CORS is one of the most misunderstood parts of frontend development, but itโ€™s actually simple once you grasp the handshake:

  1. The browser sends the Origin
  2. Server replies with Access-Control-Allow-Origin (and friends)
  3. The browser enforces the policy

โœ… Always configure CORS on the backend
โœ… Use preflight headers for custom requests
โœ… Never rely on browser plugins or no-cors mode

Once you understand CORS, debugging โ€œblocked by CORS policyโ€ becomes a 2-minute fix not a 2-hour headache.


Call to Action

Still struggling with a CORS setup in your project?
Drop your frontend + backend config in the comments ๐Ÿ‘‡ Iโ€™ll help you debug it.

And if your teammate still says โ€œCORS is a frontend issueโ€โ€ฆ
Share this post, theyโ€™ll never say it again.

Leave a Reply

Your email address will not be published. Required fields are marked *