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 *) |