Skip to content
Joey Wang
Menu

Search

Full Stack Development

React Dev Proxy Options: package.json vs http-proxy-middleware

Two ways to avoid CORS errors in local React development: the package.json proxy field for one backend, or http-proxy-middleware for several.

· 2 min read

engineering #react#javascript#networking

Audio summary

A React frontend on localhost:3000 calling a backend on localhost:5000 runs into CORS the moment the browser checks response headers for Access-Control-Allow-Origin and doesn’t find a match. The fix during development isn’t to configure CORS headers on the backend, it’s to make the browser think there’s only one origin: proxy the requests through the dev server.

The proxy field in package.json

For a single backend, this is the entire setup:

{
  "name": "my-react-app",
  "scripts": {
    "start": "react-scripts start"
  },
  "proxy": "http://localhost:5000"
}

Any request that doesn’t match a static asset in public/ gets forwarded to http://localhost:5000 with the path appended. Your components just call relative paths:

fetch('/api/users')
  .then(response => {
    if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
    return response.json();
  })
  .then(setUsers)
  .catch(setError);

It has real limits: one target only, no path rewriting, and it only applies to react-scripts start, not the production build. For production you still need Nginx, Apache, or another reverse proxy in front of the static files.

http-proxy-middleware for anything more complex

Multiple backends, path rewriting, or request/response hooks need http-proxy-middleware:

npm install http-proxy-middleware --save-dev

Create src/setupProxy.js - react-scripts picks it up automatically:

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://localhost:5000',
      changeOrigin: true,
      logger: console,
    })
  );

  app.use(
    '/auth',
    createProxyMiddleware({
      target: 'https://auth.staging.dev.com',
      changeOrigin: true,
      pathRewrite: {
        '^/auth': '',
      },
      secure: false, // dev only, self-signed certs
      logger: console,
    })
  );
};

changeOrigin: true rewrites the Host header to match the target, which most virtual-hosted backends require. pathRewrite strips or rewrites the prefix before forwarding, so /auth/login becomes /login on the target. secure: false skips TLS verification and should never leave your laptop.

Calls now target whichever context path you defined:

fetch('/api/users')      // -> http://localhost:5000/api/users
fetch('/auth/login', { method: 'POST' })  // -> https://auth.staging.dev.com/login

Which one

Use the proxy field when there’s one backend and no path games to play. Reach for http-proxy-middleware the moment you need a second target, a rewritten path, or custom request handling. Either way, it’s a development convenience: production traffic still needs a real reverse proxy in front of it.