How to Fix CORS Error in Express.js: Causes and Solutions
How to Fix CORS Error in Express.js: Causes, Solutions & Code Examples If you have ever built an API with Express.js and tried to call it from a frontend running on a different domain or port, you have almost certainly run into the dreaded CORS error. The browser console flashes a red message like “Access to fetch at … has been blocked by CORS policy” and your request silently fails. The good news: CORS errors are predictable and fixable once you understand what is happening behind the scenes. In this guide published by the GeminiWeb team, we will walk you through every common cause of CORS issues in Express.js and give you clear, copy-paste code examples for each fix. Whether you are dealing with simple requests, preflight failures, credentials, or multiple allowed origins, this post has you covered. What Is CORS and Why Does the Error Happen? CORS stands for Cross-Origin Resource Sharing. It is a security mechanism enforced by web browsers. When your frontend (e.g., https://app.example.com) makes an HTTP request to a backend on a different origin (e.g., https://api.example.com), the browser checks specific HTTP response headers to decide whether the frontend is allowed to read the response. If those headers are missing or misconfigured on your Express.js server, the browser blocks the response and throws a CORS error. Important: the server still processes the request. CORS is a browser-side enforcement, not a server-side block. Key CORS Response Headers Header Purpose Access-Control-Allow-Origin Specifies which origin(s) can access the resource Access-Control-Allow-Methods Lists the HTTP methods (GET, POST, PUT, DELETE, etc.) allowed Access-Control-Allow-Headers Lists the custom headers the client is permitted to send Access-Control-Allow-Credentials Indicates whether cookies/auth headers are allowed Access-Control-Max-Age How long (in seconds) the browser can cache the preflight response Most Common Causes of CORS Errors in Express.js Before jumping to solutions, let’s identify the root causes. In our experience at GeminiWeb working on dozens of Node.js projects, these are the issues we see most often: No CORS headers set at all on the Express server. Misconfigured Access-Control-Allow-Origin (wrong value, missing protocol, or typo). Preflight (OPTIONS) requests not handled, causing PUT/PATCH/DELETE or custom-header requests to fail. Credentials mode enabled on the client but the server uses a wildcard (*) origin. Multiple or dynamic origins not supported by the configuration. Middleware order issues where CORS headers are set after the route handler runs or after an error is thrown. Reverse proxy or hosting platform stripping headers before they reach the browser. Let’s fix each one. Solution 1: Use the cors npm Package (Quickest Fix) The fastest way to fix CORS errors in Express.js is to install the official cors middleware package. Step-by-step Install the package: npm install cors Import and use it in your Express app: const express = require(‘express’); const cors = require(‘cors’); const app = express(); // Enable CORS for all origins app.use(cors()); app.get(‘/api/data’, (req, res) => { res.json({ message: ‘CORS is working!’ }); }); app.listen(3000, () => { console.log(‘Server running on port 3000’); }); This adds Access-Control-Allow-Origin: * to every response. It is great for development, but not recommended for production because it allows any website to read your API responses. Solution 2: Allow a Specific Origin For production, you should restrict access to trusted domains only. app.use(cors({ origin: ‘https://myapp.example.com’ })); Now only requests originating from https://myapp.example.com will receive the proper CORS headers. Requests from any other origin will be blocked by the browser. Common mistake: forgetting to include the protocol. myapp.example.com without https:// will not match and the error will persist. Solution 3: Allow Multiple Origins If your API serves several frontends (e.g., a marketing site and a dashboard), you need to dynamically set the origin header. const allowedOrigins = [ ‘https://myapp.example.com’, ‘https://dashboard.example.com’, ‘http://localhost:5173’ ]; app.use(cors({ origin: function (origin, callback) { // Allow requests with no origin (e.g., mobile apps, curl) if (!origin) return callback(null, true); if (allowedOrigins.includes(origin)) { return callback(null, true); } else { return callback(new Error(‘Not allowed by CORS’)); } } })); Pro tip: store your allowed origins in an environment variable so you can change them without redeploying. # .env CORS_ORIGINS=https://myapp.example.com,https://dashboard.example.com const allowedOrigins = process.env.CORS_ORIGINS.split(‘,’); Solution 4: Handle Preflight Requests Properly What is a preflight request? Before sending certain requests (e.g., PUT, PATCH, DELETE, or any request with custom headers like Authorization), the browser sends a preliminary OPTIONS request called a preflight. If your server does not respond to this OPTIONS request with the correct headers, the actual request never fires. Fix with the cors package If you are using app.use(cors()) before your routes, preflight is handled automatically. But if you applied CORS only to specific routes, you also need to handle OPTIONS: // Enable preflight for all routes app.options(‘*’, cors()); // Then apply cors to your specific route app.get(‘/api/data’, cors(), (req, res) => { res.json({ message: ‘Hello’ }); }); Fix without the cors package (manual headers) app.use((req, res, next) => { res.header(‘Access-Control-Allow-Origin’, ‘https://myapp.example.com’); res.header(‘Access-Control-Allow-Methods’, ‘GET, POST, PUT, DELETE, OPTIONS’); res.header(‘Access-Control-Allow-Headers’, ‘Content-Type, Authorization’); // Handle preflight if (req.method === ‘OPTIONS’) { return res.sendStatus(204); } next(); }); Returning a 204 No Content status for OPTIONS tells the browser “go ahead, the actual request is allowed.” Solution 5: Fix CORS When Using Credentials (Cookies / Auth Headers) If your frontend sends cookies or an Authorization header, you need two things: The client must set credentials: ‘include’ (Fetch API) or withCredentials: true (Axios). The server must set Access-Control-Allow-Credentials: true and must not use a wildcard * for the origin. Client-side (Axios example) axios.get(‘https://api.example.com/user’, { withCredentials: true }); Server-side app.use(cors({ origin: ‘https://myapp.example.com’, credentials: true })); If you use origin: ‘*’ together with credentials: true, the browser will reject the response. This is one of the most frequent mistakes we see. Solution 6: Expose Custom Response Headers By default, the browser only exposes a limited set of response headers to JavaScript. If your API returns a custom header (like X-Total-Count for pagination), you need to explicitly expose it: app.use(cors({ origin: ‘https://myapp.example.com’, exposedHeaders: [‘X-Total-Count’, ‘X-Request-Id’] })); Solution 7: Fix Middleware Order Issues Express processes middleware in the order it
How to Fix CORS Error in Express.js: Causes and Solutions Read More »
