
How to Fix CORS Errors: A Developers Practical Troubleshooting Guide
If you have spent time building web applications, you have likely encountered this browser error: "Access to fetch at from origin has been blocked by CORS policy".
Cross-Origin Resource Sharing (CORS) is one of the most common hurdles for full-stack developers. It is not a backend bug, nor is it a hosting issue. It is a security feature enforced by the browser.
In this guide, we will break down what CORS is, explain why it blocks your requests, and provide practical solutions for Node.js, Nginx, and frontend development servers.
What is CORS?
CORS is a mechanism that allows servers to declare which origins (combinations of protocol, domain, and port) are permitted to read resources from that server.
By default, web browsers enforce the Same-Origin Policy (SOP). This policy prevents a script loaded from Origin A (e.g., http://localhost:3000) from querying resources from Origin B (e.g., https://api.example.com) unless Origin B explicitly grants permission using specific HTTP response headers.
How CORS Works: Preflight Requests
When a frontend application makes a cross-origin request, the browser classifies it as either a Simple Request or a Preflight Request.
For requests that modify data (such as PUT, DELETE, or POST with custom JSON headers), the browser automatically triggers an initial OPTIONS request (the preflight request) to the server. The preflight checks if the server supports the cross-origin action.
The server must respond to the OPTIONS request with headers indicating permission:
Access-Control-Allow-Origin: Specifies which frontend domain is allowed.Access-Control-Allow-Methods: Lists allowed HTTP methods (e.g., GET, POST, OPTIONS).Access-Control-Allow-Headers: Lists allowed custom request headers (e.g., Authorization, Content-Type).
If the server approves the preflight, the browser fires the actual request. If the server does not respond with correct headers, the browser blocks the data from reaching your frontend code.
Solution 1: Resolving CORS in Node.js (Express)
The simplest way to resolve CORS is to configure headers directly in your backend application. In Node.js Express, you can use the official cors middleware package.
First, install the package:
pnpm add corsNext, configure the middleware in your application entry file:
// server.ts
import express from 'express';
import cors from 'cors';
const app = express();
// Configure CORS options
const corsOptions = {
origin: 'http://localhost:3000',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
};
app.use(cors(corsOptions));
app.get('/api/data', (req, res) => {
res.json({ message: 'Success' });
});
app.listen(5000);Solution 2: Resolving CORS in Nginx Proxy Configurations
If you cannot modify the backend code directly, you can configure your Nginx reverse proxy to inject the required CORS headers and handle OPTIONS preflight requests automatically.
Add the following config block inside your Nginx server block:
server {
listen 80;
server_name api.example.com;
location / {
# Handle OPTIONS preflight requests
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
add_header 'Access-Control-Max-Age' 1728000 always;
add_header 'Content-Type' 'text/plain; charset=utf-8' always;
add_header 'Content-Length' 0 always;
return 204;
}
# Inject headers for regular requests
add_header 'Access-Control-Allow-Origin' 'https://example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
proxy_pass http://localhost:5000;
}
}Solution 3: Using a Local Development Proxy
If you are developing locally and the API is owned by a third party that you cannot modify, you can bypass CORS by configuring a dev proxy in your frontend build tool (e.g., Vite).
The browser blocks cross-origin requests, but servers do not. By routing requests through a local development proxy server, you make SOP-compliant requests to your local dev server, which acts as a bridge to retrieve the third-party data.
Update your vite.config.ts file:
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
});Now, instead of fetching https://api.example.com/users, your frontend code queries /api/users. Vite proxies the request behind the scenes, eliminating CORS hurdles in local development.
Conclusion
CORS is an essential web security guard, not a bug. Understanding the difference between simple and preflight OPTIONS requests allows you to configure CORS rules correctly. Use the Express middleware for application-level control, configure Nginx for proxy-level control, or configure Vite dev proxies for local development hurdles, keeping your APIs secure and accessible.