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:
- The browser sends the Origin
- Server replies with Access-Control-Allow-Origin (and friends)
- 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