Proxy-Based Frontend-Backend Integration

In a split-stack project (separate frontend and backend processes on different ports), configure the frontend dev server to proxy API requests to the backend rather than hardcoding backend URLs or relying on CORS alone. The proxy eliminates cross-origin issues during development, keeps the frontend...

Tags

Proxy-Based Frontend-Backend Integration

The Lesson

In a split-stack project (separate frontend and backend processes on different ports), configure the frontend dev server to proxy API requests to the backend rather than hardcoding backend URLs or relying on CORS alone. The proxy eliminates cross-origin issues during development, keeps the frontend decoupled from deployment topology, and mirrors production behavior where both are typically served from the same origin.

Context

A diagram platform ran a FastAPI backend on port 8019 and a Vite + React frontend on port 4339. The frontend needed to call 15+ REST endpoints for project management, entity CRUD, diagram rendering, and exports. During development, the browser's same-origin policy would block requests from localhost:4339 to localhost:8019 unless CORS headers were configured or a proxy was used.

What Happened

  1. The backend was configured with permissive CORS middleware (allow_origins=["*"]) as a safety net for direct browser-to-API access (e.g., Swagger UI at localhost:8019/docs).
  2. The frontend's vite.config.ts was configured with a proxy rule: any request to /api/* is rewritten and forwarded to http://localhost:8019 with the /api prefix stripped.
  3. The frontend API client (lib/api.ts) uses relative URLs with an /api prefix: fetch("/api/projects"), fetch("/api/projects/${name}/entities"). No hardcoded host, port, or protocol.
  4. In the browser, the request flow is: localhost:4339/api/projects → Vite dev server proxy → localhost:8019/projects → FastAPI → response. The browser sees a same-origin response with no CORS involvement.
  5. For production deployment, the same relative /api URLs would work behind a reverse proxy (nginx, Caddy) that routes /api/* to the backend and everything else to the frontend's static build.

Key Insights

Examples

Vite proxy configuration:

// vite.config.ts
export default defineConfig({
  server: {
    port: 4339,
    proxy: {
      "/api": {
        target: "http://localhost:8019",
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ""),
      },
    },
  },
});

Frontend API call (no hardcoded URL):

const res = await fetch("/api/projects");

Production nginx equivalent:

location /api/ {
    proxy_pass http://backend:8019/;
}
location / {
    root /usr/share/nginx/html;
    try_files $uri /index.html;
}

Applicability

This pattern applies to any project with a separate frontend dev server and backend API: React + FastAPI, Vue + Express, Svelte + Go, etc. It does NOT apply to monolithic frameworks where the backend serves the frontend directly (e.g., Django with templates, Rails with ERB) — there, same-origin is already guaranteed by architecture.

Related Lessons

Related Lessons