What is a CORS Error, and How to Fix It in an Express.js Environment?

Every modern web developer has encountered the threatening red error message flashing on the browser console: "Access to fetch at '...' from origin '...' has been blocked by CORS policy". This error occurs when a frontend application running in the browser (e.g., a React app on localhost:3000) tries to request data from a backend located on a different domain or port (e.g., localhost:8080) using AJAX or the Fetch API.

What Exactly is CORS and Why Does it Exist?

CORS (Cross-Origin Resource Sharing) is a crucial security mechanism enforced by web browsers. It is an exception system to the fundamental security rule known as SOP (Same-Origin Policy).

The goal of SOP is to prevent scripts of a malicious website (which the user is currently reading) from secretly stealing data or performing actions on another website (such as their online bank) where the user is currently logged in. By default, the browser blocks any request where the source's (origin's) protocol (http/https), domain name, or port differs from that of the target.

The Preflight Request (OPTIONS)

When the client (browser) sends a "non-simple" request (for example, a POST request with a JSON body, or with custom headers) to another origin, the browser first sends an invisible OPTIONS request, called a preflight request, to the server. By doing this, it "asks" the server: "Hello, I'm coming from the site https://simplesolution.ro, and I want to send a POST request with JSON. Do you allow it?" If the server doesn't respond with the appropriate permitting headers, the browser blocks the actual request (CORS error).

Resolving the CORS Error in a Node.js / Express Environment

Since CORS is checked by the browser based on the server's response, we need to indicate on the server side in the response headers (Headers) which origins we allow access to. In an Express.js environment, this can be done most easily using the official cors NPM package (middleware):

const express = require('express');
// The cors package needs to be installed: npm install cors
const cors = require('cors'); 
const app = express();

// Solution 1 (For Development): Allowing all origins
// app.use(cors()); 

// Solution 2 (Production environment / Secure approach)
const corsOptions = {
    origin: ['https://simplesolution.ro', 'https://www.simplesolution.ro'], // We only allow requests from these domains
    methods: ['GET', 'POST', 'PUT', 'DELETE'], // Allowed HTTP methods
    allowedHeaders: ['Content-Type', 'Authorization'], // Allowed custom headers
    optionsSuccessStatus: 200 // For compatibility with older browsers
};

app.use(cors(corsOptions));

app.get('/api/v1/data', (req, res) => {
    res.json({ message: "Successful, CORS-safe data connection!" });
});

app.listen(8080, () => console.log('Server is running on port 8080'));

Summary

During the development phase, using app.use(cors()) without parameters is the most convenient, as it allows any origin (Access-Control-Allow-Origin: *). However, in a Production environment, this can be a serious security flaw. In such cases, as in the example above, always restrict the allowed domains to the domains of your own frontend.

Frequently Asked Questions (FAQ)

Why does Postman never throw a CORS error?

Postman, cURL, and other server-to-server communication tools are not web browsers. CORS is a browser-level security feature. Postman simply does not check SOP and does not send Preflight requests, which is why API calls from there always work.


You Might Also Like: