CORS and Vite Proxy — Connecting Dev Environments

When the React dev server on port 5173 makes a request to FastAPI on port 8000, the browser applies the Same-Origin Policy and blocks the response unless FastAPI includes the correct Access-Control-Allow-Origin header. This is CORS (Cross-Origin Resource Sharing). There are two solutions: configure FastAPI to send CORS headers (needed in production), or configure Vite to proxy API requests through its own server (convenient in development — the browser sees one origin). Both are often used together.

FastAPI CORS Configuration

# app/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI(title="Blog API")

# ── CORS middleware ─────────────────────────────────────────────────────────────
app.add_middleware(
    CORSMiddleware,
    # Development: allow the React Vite dev server
    # Production: allow your actual frontend domain(s)
    allow_origins=[
        "http://localhost:5173",        # Vite dev server
        "http://localhost:3000",        # Create React App (if used)
        "https://blog.example.com",     # production domain
    ],
    allow_credentials = True,           # allow cookies and auth headers
    allow_methods     = ["*"],          # GET, POST, PATCH, DELETE, etc.
    allow_headers     = ["*"],          # Authorization, Content-Type, etc.
)
Note: allow_credentials=True requires allow_origins to list specific origins — you cannot use allow_origins=["*"] (wildcard) when credentials are allowed. This is a security requirement of the CORS specification: if the server allows any origin AND credentials, any malicious site could make authenticated requests on behalf of your users. Always list specific allowed origins in production.
Tip: In production, load allowed origins from an environment variable so you do not need to change code between environments: CORS_ORIGINS=https://blog.example.com,https://www.blog.example.com. In FastAPI: origins = settings.cors_origins.split(",") if settings.cors_origins else []. This also lets you add new frontend domains (staging, preview deployments) without redeploying the backend.
Warning: Never use allow_origins=["*"] in production for APIs that handle authenticated requests. A wildcard CORS policy means any website in the world can make requests to your API — including malicious sites that trick logged-in users into visiting them (CSRF-style attacks). Restrict allowed origins to the specific domains that are actually your frontend applications.

Vite Proxy Configuration

// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path  from "path";

export default defineConfig({
    plugins: [react()],
    resolve: {
        alias: { "@": path.resolve(__dirname, "src") },
    },
    server: {
        port: 5173,
        proxy: {
            // Any request to /api/* is forwarded to FastAPI
            "/api": {
                target:       "http://localhost:8000",
                changeOrigin: true,
                // If FastAPI routes don't have /api prefix, rewrite:
                // rewrite: (path) => path.replace(/^\/api/, ""),
            },
            // Forward static file requests (uploaded images, etc.)
            "/uploads": {
                target:       "http://localhost:8000",
                changeOrigin: true,
            },
        },
    },
});

How the Proxy Works

Without proxy (CORS error):
  Browser fetches http://localhost:5173/api/posts
  → Browser sends request to http://localhost:8000/api/posts  ← CROSS-ORIGIN
  → FastAPI returns response WITHOUT CORS headers (if not configured)
  → Browser BLOCKS the response with CORS error

With Vite proxy:
  Browser fetches http://localhost:5173/api/posts
  → Vite dev server (same origin!) intercepts the request
  → Vite server-side forwards to http://localhost:8000/api/posts
  → FastAPI responds to Vite (server-to-server, no CORS check)
  → Vite returns the response to the browser as if from localhost:5173
  → Browser sees ONE origin (localhost:5173) — no CORS issue!

Which Approach to Use

Scenario Approach
Local development Vite proxy (simpler, no CORS headers needed)
Production deployment FastAPI CORS middleware (required)
Both Use CORS middleware + proxy — proxy in dev, CORS headers in prod
Mobile app or Postman client CORS middleware (no proxy possible)

Common Mistakes

Mistake 1 — allow_origins=[“*”] with allow_credentials=True

❌ Wrong — FastAPI raises a ValueError at startup:

CORSMiddleware(allow_origins=["*"], allow_credentials=True)
# ValueError: Cannot use allow_credentials=True with wildcard origins

✅ Correct — list specific origins when credentials are needed.

Mistake 2 — Forgetting the Vite proxy and hardcoding localhost:8000

❌ Wrong — hardcoded URL fails in production:

axios.create({ baseURL: "http://localhost:8000/api" });   // breaks in production!

✅ Correct — use a relative URL (/api) that the proxy or Nginx handles:

axios.create({ baseURL: "/api" });   // ✓ works with Vite proxy in dev, Nginx in prod

Quick Reference

Task Code
FastAPI CORS app.add_middleware(CORSMiddleware, allow_origins=[...], ...)
Vite proxy server.proxy: { "/api": { target: "http://localhost:8000" } }
Relative base URL baseURL: "/api" — works with both proxy and CORS
Allow credentials allow_credentials=True + specific origins (not *)

🧠 Test Yourself

The Vite proxy forwards /api requests to FastAPI. Does FastAPI still need CORS middleware configured for the React dev app?