Skip to main content

The error you’ll definitely see

The first time your React frontend tries to call your FastAPI backend, you’ll see this in the browser console:
Access to fetch at 'http://localhost:8000/api/users' from origin
'http://localhost:5173' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.
This is the most common error in full-stack development. It looks scary, but the fix is simple.

What is CORS?

CORS (Cross-Origin Resource Sharing) is a browser security feature. It prevents a website from making requests to a different domain/port unless that server explicitly allows it. An origin is a combination of protocol + domain + port:
URLOrigin
http://localhost:5173http://localhost:5173
http://localhost:8000http://localhost:8000
https://myapp.comhttps://myapp.com
https://api.myapp.comhttps://api.myapp.com
Your React frontend (localhost:5173) and your FastAPI backend (localhost:8000) have different origins because they’re on different ports. The browser blocks the request by default.
CORS is a browser-only security feature. It doesn’t affect Postman, curl, or server-to-server requests. That’s why your API works fine in FastAPI’s Swagger UI (/docs) or when you test with curl, but breaks when called from React.
The companion repo allows more than one dev origin because the same API is used by multiple clients: http://localhost:5173 (Vite web frontend) and http://localhost:8081 (Expo web while testing the mobile app).

Why CORS exists

Without CORS, any website could make requests to any other site using your credentials. Imagine visiting a malicious site that silently makes requests to your bank’s API using cookies your browser already has stored. CORS prevents this by requiring servers to opt in to receiving requests from other origins.

The fix: FastAPI CORS middleware

Tell FastAPI to accept requests from your React frontend:
# backend/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173"],  # React dev server
    allow_credentials=True,
    allow_methods=["*"],     # Allow all HTTP methods
    allow_headers=["*"],     # Allow all headers
)

# ... your routes below
That’s it. Add these 7 lines and the CORS error disappears.

What each option does

OptionValueMeaning
allow_origins["http://localhost:5173"]Which frontends can make requests
allow_credentialsTrueAllow cookies and auth headers
allow_methods["*"]Allow GET, POST, PUT, DELETE, etc.
allow_headers["*"]Allow Content-Type, Authorization, etc.

Development vs production origins

In development, your frontend runs at localhost:5173. In production, it runs at your actual domain. Handle both:
import os

# Read from environment variable
ALLOWED_ORIGINS = os.getenv(
    "ALLOWED_ORIGINS",
    "http://localhost:5173"
).split(",")

app.add_middleware(
    CORSMiddleware,
    allow_origins=ALLOWED_ORIGINS,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
# backend/.env (development)
ALLOWED_ORIGINS=http://localhost:5173

# backend/.env (production)
ALLOWED_ORIGINS=https://myapp.com,https://www.myapp.com
Never use allow_origins=["*"] in production. This allows any website to call your API — the exact thing CORS was designed to prevent. Always list your specific frontend URL(s).

How CORS actually works

When your browser makes a cross-origin request, it adds an Origin header:
GET /api/users HTTP/1.1
Origin: http://localhost:5173
The server responds with an Access-Control-Allow-Origin header:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:5173
If the origin matches, the browser allows the response through. If not, it blocks it. For “complex” requests (POST with JSON, PUT, DELETE), the browser first sends a preflight request — an OPTIONS request to check if the actual request is allowed:
1. Browser sends OPTIONS /api/users (preflight)
2. Server responds with allowed methods/headers
3. Browser sends actual POST /api/users
4. Server responds with data
The CORS middleware handles all of this automatically. You don’t need to think about preflight requests.

Debugging CORS errors

If you still see CORS errors after adding the middleware:
1

Check the origin matches exactly

http://localhost:5173 is not the same as http://localhost:5173/ (trailing slash) or http://127.0.0.1:5173 (IP vs hostname).
2

Make sure middleware is added before routes

The CORS middleware must be added before your route definitions. FastAPI processes middleware in order.
3

Check the Network tab

Open DevTools → Network tab. Look at the failing request. Check the Response Headers for Access-Control-Allow-Origin. If it’s missing, the middleware isn’t configured correctly.
4

Restart your backend

Environment variable changes require restarting the server. uvicorn --reload only watches for file changes, not .env changes.
CORS errors always appear in the browser console, never in the backend terminal. If your backend terminal shows no errors but the browser does, it’s almost certainly CORS.

What’s next?

Your frontend can talk to your backend. But right now, fetch() calls are scattered throughout your components. Let’s organize them into a dedicated API client layer.

API client layer

Separate API calls from components for cleaner, more maintainable code