Originally published on CodingCush.

CORS, short for Cross-Origin Resource Sharing, is the browser rule that decides whether JavaScript on one website may read data from another site's server. When you see the console error "blocked by CORS policy", it means your page asked an API for data, the API answered, and the browser refused to hand the answer to your code because the server did not give permission. In this guide you will learn what an origin is, why the browser blocks first and asks questions later, what a preflight request is, and how to fix CORS properly on the server, with a small Node.js example we tested live with curl.

First, What Is an Origin?

An origin is the combination of three things: the scheme, the host and the port. https://codingcush.com and http://codingcush.com are different origins because the scheme differs. https://codingcush.com and https://api.codingcush.com are different origins because the host differs. Even http://localhost:3000 and http://localhost:5000, your own laptop twice, are different origins because the port differs. Two pages with the same origin may freely read each other's responses. Cross that line, and CORS becomes the gatekeeper.

Here is the part that surprises everyone: the request usually still reaches the server, and the server often answers correctly. You can prove it in the Network tab, and it is why the same URL works in curl or Postman. CORS is a browser protection, not a server firewall. It exists so that a random website you visit cannot use your browser, and your logged-in sessions, to quietly read data from other sites. The server must opt in, out loud, in its response headers, before the browser will share the response with the page's JavaScript.

The One Header That Fixes Most Cases

Permission is given with a response header called Access-Control-Allow-Origin. When your browser sends a request, it adds an Origin header saying where the page came from. The server replies, and if the response contains Access-Control-Allow-Origin naming your origin (or the wildcard * for public data), the browser releases the response to your code. No matching header, no data. The MDN guide to Cross-Origin Resource Sharing describes this handshake in full, and it is worth a careful read once the basics below feel familiar.

These headers belong to the same family as the request and response headers you meet every day, so if names like Host and Content-Type are new, our guide to HTTP headers explained: what they are and how they work is the friendly place to start.

Simple Requests vs Preflighted Requests

Not every cross-origin request is treated the same way:

Simple request Preflighted request
Methods GET, HEAD, or POST Any, including PUT, PATCH, DELETE
Headers Only simple ones, such as a plain form-style Content-Type Custom headers, or Content-Type: application/json
Extra step None: the request is sent at once The browser first sends an OPTIONS preflight to ask permission
Typical case Fetching public JSON with a plain GET Sending JSON or an Authorization header to an API

The preflight is the browser asking, before the real request: "A page from this origin wants to send a POST with a JSON body and these headers. Is that acceptable?" The server answers the OPTIONS request with Access-Control-Allow-Origin, Access-Control-Allow-Methods and Access-Control-Allow-Headers. Only a yes lets the real request follow. This is why your server logs sometimes show an OPTIONS hit you never wrote code for. The browser sent it, politely, on your behalf.

A Working Fix: A Tiny Node.js API with CORS Headers

Here is a complete API with no framework, tested for this article. It sets the three headers on every response and answers preflights with a 204:

const http = require("http"); const server = http.createServer((req, res) => { res.setHeader("Access-Control-Allow-Origin", "https://codingcush.com"); res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "Content-Type"); if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; } res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify({ message: "Hello from the API" })); }); server.listen(3456, () => { console.log("API listening on http://127.0.0.1:3456"); }); 

Two habits in this code matter in real projects. First, the allowed origin is one named site, not *. The wildcard is fine for a fully public API, but it cannot be combined with credentials such as cookies, and it invites every website on the internet to read your responses. Second, the headers are set before any branching, so error responses carry them too; a 500 without CORS headers produces the same confusing browser error as a blocked 200.

We verified the server with curl, sending the same Origin header a browser would send:

curl -i -H "Origin: https://codingcush.com" http://127.0.0.1:3456/data 

Real output from that test:

HTTP/1.1 200 OK Access-Control-Allow-Origin: https://codingcush.com Access-Control-Allow-Methods: GET, POST, OPTIONS Access-Control-Allow-Headers: Content-Type Content-Type: application/json {"message":"Hello from the API"} 

And the preflight test, an OPTIONS request, answered HTTP/1.1 204 No Content with the same three Access-Control headers. If your own API fails this curl test, no browser setting will save you; the fix belongs on the server. The reference page for the Access-Control-Allow-Origin header on MDN lists every valid form of the header when you need the exact rules.

Terminal window showing the real curl test of the Node.js CORS server in this article: a 200 response carrying Access-Control-Allow-Origin for codingcush.com, and a 204 preflight answer

The real curl test of the CORS server in this article: the normal request returns 200 with Access-Control-Allow-Origin naming the allowed site, and the OPTIONS preflight returns 204 with the same permission headers.

How This Looks from the Fetch Side

From the page's point of view, nothing about your code changes. A normal call such as the ones in our guide to how to fetch data from an API in JavaScript works as soon as the server grants permission:

fetch("https://api.example.com/data") .then((response) => response.json()) .then((data) => console.log(data)) .catch((error) => console.log("Request failed:", error.message)); 

Notice that a CORS failure lands in catch with a plain network-style error. JavaScript is deliberately told almost nothing about why, for security reasons; the real explanation lives only in the browser console. So when fetch fails and the server looks healthy, read the console message carefully: it names the origin, the missing header, or the failed preflight, and that sentence is your actual bug report. If APIs themselves are still new to you, start with our explainer on what an API is, explained simply, then come back to the gatekeeper.

Common Mistakes and Troubleshooting

Trying to fix CORS from the frontend. There is no frontend fix, by design. Adding headers to your fetch request does not grant permission; only the server's response headers do. If the API is not yours, you need the API owner to allow your origin, or a small backend of your own that calls the API server-side, where CORS does not apply.

Using the wildcard with credentials. Access-Control-Allow-Origin: * plus cookies or an Authorization flow will still be blocked. Name the exact origin, and have the server also send Access-Control-Allow-Credentials: true when cookies must travel.

Answering the GET but not the OPTIONS. If your framework route handles POST but the preflight OPTIONS falls through to a 404, the browser stops before the real request. Test preflights with curl exactly as shown above.

Blaming CORS for a plain network failure. If curl cannot reach the server either, the problem is DNS, a stopped server, or a wrong URL, not CORS. CORS errors only appear when the server answered and the browser withheld the answer.

Installing a browser extension to "disable CORS". Fine for a five-minute local test, dangerous as a habit, and it fixes nothing for your visitors. Always land the real fix on the server.

Frequently Asked Questions

Why does the same API work in Postman but not in the browser?

Postman and curl are not browsers, so they do not enforce CORS. They show you the raw response. The browser adds the permission check on top, which is why "it works in Postman" is the classic CORS symptom.

Is CORS a security feature of my server?

It protects users and other websites, inside the browser. It does not stop anyone from calling your API with other tools, so never treat CORS as your API's security. Authentication and rate limits are separate jobs.

What does "preflight" mean in simple words?

Before sending a non-simple cross-origin request, the browser sends a small OPTIONS request asking the server for permission, listing the method and headers it plans to use. A correct answer lets the real request through.

Can I allow several origins?

Yes. The header holds one origin per response, so servers usually check the incoming Origin header against an allow-list and echo back whichever one matched. Frameworks and CORS middleware packages automate exactly that check.

Conclusion

CORS is not your API being broken; it is your browser asking for a permission slip the server forgot to sign. Name your origin in Access-Control-Allow-Origin, answer preflights with the methods and headers your API accepts, verify both with curl, and the red console error turns back into the quiet, working fetch you expected all along.