The Problem

During local development, your frontend (e.g., http://localhost:3000) makes API requests to a backend on a different port (e.g., http://localhost:8080). The browser blocks these requests due to CORS (Cross-Origin Resource Sharing) policy, even though both are on localhost.

Symptoms

  • Console error: "Access to fetch has been blocked by CORS policy"
  • Network tab shows the request completes with status 200
  • Response is empty or undefined in JavaScript
  • Works with curl or Postman but not from the browser

Real Error Message

bash
Access to fetch at 'http://localhost:8080/api/users' from origin
'http://localhost:3000' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource. If an opaque response serves your needs, set the request's
mode to 'no-cors' to fetch the resource with CORS disabled.

Why CORS Applies to Localhost

CORS is enforced based on origin (protocol + host + port). http://localhost:3000 and http://localhost:8080 are DIFFERENT origins because the ports differ.

How to Fix It

Fix 1: Add CORS Headers to Backend (Node.js/Express)

```javascript const cors = require('cors'); const express = require('express'); const app = express();

// Allow all origins (development only!) app.use(cors({ origin: 'http://localhost:3000', credentials: true }));

app.get('/api/users', (req, res) => { res.json({ users: [] }); }); ```

Fix 2: Use Dev Server Proxy (Vite)

javascript
// vite.config.js
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        secure: false
      }
    }
  }
});

Now the frontend calls /api/users (same origin) and Vite proxies it to the backend.

Fix 3: Use Dev Server Proxy (Create React App)

json
// package.json
{
  "proxy": "http://localhost:8080"
}

Fix 4: Use Dev Server Proxy (Webpack)

javascript
// webpack.config.js
module.exports = {
  devServer: {
    proxy: {
      '/api': 'http://localhost:8080'
    }
  }
};

Fix 5: Temporary Browser Flag (Development Only)

```bash # Chrome (DISABLES ALL CORS - dangerous!) chrome --disable-web-security --user-data-dir=/tmp/chrome-dev

# NEVER use this for regular browsing ```

Fix 6: Handle Preflight OPTIONS Requests

javascript
// Backend must respond to OPTIONS preflight
app.options('/api/*', (req, res) => {
  res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.header('Access-Control-Allow-Credentials', 'true');
  res.sendStatus(204);
});

Fix 7: Debug CORS with curl

```bash # Test preflight curl -X OPTIONS http://localhost:8080/api/users \ -H "Origin: http://localhost:3000" \ -H "Access-Control-Request-Method: GET" \ -H "Access-Control-Request-Headers: Authorization" \ -v

# Check response headers curl -v http://localhost:8080/api/users \ -H "Origin: http://localhost:3000" ```