DEV Community

KAMAL KISHOR
KAMAL KISHOR

Posted on

How to Fix CORS Error in React (The Right Way with Proxy Middleware)

If you’ve ever built a React app and tried calling an external API, chances are you’ve run into the dreaded CORS error:

Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:5173' 
has been blocked by CORS policy
Enter fullscreen mode Exit fullscreen mode

Sound familiar? Don’t worry—you’re not alone. In this article, I’ll walk you through why this happens and how to fix it properly using a Node/Express proxy middleware. This method works in both development and production and is the most reliable way to deal with CORS.


What is CORS? (Quick Recap)

  • CORS = Cross-Origin Resource Sharing.
  • Browsers block requests from one domain (e.g., http://localhost:5173) to another (e.g., https://api.example.com) unless the server explicitly allows it.
  • You cannot fix CORS errors from the frontend. Only the server can allow cross-origin requests.

👉 The solution: Use a proxy server.


How Does a Proxy Fix CORS?

React App (http://localhost:5173) → /proxy → Express Middleware → https://dummyapi.com
Enter fullscreen mode Exit fullscreen mode
  • React calls /proxy → same origin → no CORS.
  • Express receives the request, forwards it to the real API (https://dummyapi.com).
  • Express sends the API response back to React.
  • Browser is happy. 🎉

Step 1 — Express Proxy Middleware

Install Express:

npm install express
npm install -D typescript ts-node @types/express nodemon
Enter fullscreen mode Exit fullscreen mode

server.ts

import express, { Request, Response, NextFunction } from 'express';

const app = express();

app.use('/proxy', async (req: Request, res: Response, next: NextFunction) => {
  try {
    const targetBase = 'https://dummyapi.com'; // Dummy external API
    const proxiedPath = req.originalUrl.replace(/^\/proxy/, '') || '/';
    const targetUrl = `${targetBase}${proxiedPath}`;

    const outgoingHeaders: Record<string, string> = {};
    Object.entries(req.headers).forEach(([k, v]) => {
      const key = k.toLowerCase();
      if (['host', 'connection', 'keep-alive', 'transfer-encoding', 'content-encoding'].includes(key)) return;
      if (typeof v === 'string') outgoingHeaders[key] = v;
      else if (Array.isArray(v)) outgoingHeaders[key] = v.join(',');
    });

    const fetchOptions: any = {
      method: req.method,
      headers: outgoingHeaders,
      redirect: 'manual',
      body: ['GET', 'HEAD'].includes(req.method) ? undefined : (req as any),
    };

    const upstreamRes = await fetch(targetUrl, fetchOptions);

    res.status(upstreamRes.status);
    upstreamRes.headers.forEach((value, name) => {
      if (!['transfer-encoding', 'connection', 'keep-alive', 'content-encoding'].includes(name.toLowerCase())) {
        res.setHeader(name, value as string);
      }
    });

    const body = upstreamRes.body;
    if (body && typeof (body as any).pipe === 'function') {
      (body as any).pipe(res);
    } else {
      const text = await upstreamRes.text();
      res.send(text);
    }
  } catch (err) {
    next(err);
  }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Proxy server running on http://localhost:${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Step 2 — Configure Vite Proxy (Dev Mode)

vite.config.ts

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/proxy': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        secure: false,
      },
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

Step 3 — Use .env in React

.env

VITE_API_BASE_URL=/proxy
Enter fullscreen mode Exit fullscreen mode

API client:

import axios from 'axios';

export const api = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
});
Enter fullscreen mode Exit fullscreen mode

Step 4 — Call API in React

import { useEffect, useState } from 'react';
import { api } from './lib/api';

function App() {
  const [data, setData] = useState<any[]>([]);

  useEffect(() => {
    api.get('/posts') // Actually calls http://localhost:3000/proxy/posts
      .then((res) => setData(res.data))
      .catch(console.error);
  }, []);

  return (
    <div>
      <h1>Posts from Dummy API</h1>
      <ul>
        {data.map((item) => (
          <li key={item.id}>{item.title}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Step 5 — Run Locally

Run backend:

npx ts-node server.ts
Enter fullscreen mode Exit fullscreen mode

Run frontend:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Visit:

✅ No CORS errors.


Step 6 — Production Deployment Guide 🚀

Here are three easy options:


🔹 Option A: Serve React from Express (Single Origin)

  1. Build React app:
   npm run build
Enter fullscreen mode Exit fullscreen mode

This generates a dist/ folder.

  1. Update Express to serve static files:
   import path from 'path';

   app.use(express.static(path.join(__dirname, '../dist')));
   app.get('*', (req, res) => {
     res.sendFile(path.join(__dirname, '../dist/index.html'));
   });
Enter fullscreen mode Exit fullscreen mode
  1. Deploy this single Express app to Render, Railway, or your own VPS. Everything runs on one origin (e.g., https://myapp.onrender.com).

✅ No extra configuration required.


🔹 Option B: React on Netlify/Vercel + Express on Render

  • Deploy React frontend on Netlify/Vercel.
  • Deploy your Express proxy server on Render (free tier available).
  • Configure Netlify/Vercel rewrites:

netlify.toml

[[redirects]]
  from = "/proxy/*"
  to = "https://your-express-service.onrender.com/proxy/:splat"
  status = 200
Enter fullscreen mode Exit fullscreen mode

✅ Now React /proxy calls will hit your Express proxy on Render.


🔹 Option C: Reverse Proxy with Nginx

If hosting yourself (VPS, AWS, DigitalOcean):

server {
  server_name myapp.com;

  location / {
    root /var/www/myapp/dist;
    index index.html;
    try_files $uri /index.html;
  }

  location /proxy/ {
    proxy_pass http://localhost:3000/proxy/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}
Enter fullscreen mode Exit fullscreen mode

/proxy is transparently forwarded to Express.
✅ Frontend served by Nginx.


Step 7 — Security Best Practices

  • Lock proxy target (no open proxies).
  • Timeouts & retries (avoid hanging requests).
  • Rate limiting (protect from abuse).
  • API keys stay on server (never expose in frontend).
  • HTTPS everywhere (secure traffic).

Final Thoughts

CORS errors frustrate almost every React developer at some point. Instead of disabling security or using hacks, the proxy middleware pattern is the most robust, production-ready solution.

  • Works locally & in production.
  • Keeps frontend code clean.
  • Adds a security layer (hide API keys).

Whether you deploy on Render, Netlify/Vercel, or your own server, this setup ensures your React app works seamlessly without CORS headaches.

Top comments (1)

Collapse
 
hassamdev profile image
Hassam Fathe Muhammad

A solid approach! Good that you went beyond the usual dev only proxy setup and showed a production friendly Express Proxy with header handling. Nice