Back to list
FastAPICORSReactAPIRouterPythonSetup

[FastAPI][Setup] CORS Middleware and Modular API Routers for React

Fix browser CORS blocks between React and FastAPI, then split endpoints with APIRouter so main.py stays maintainable as the API grows.

1. Environment

  • OS: Windows 11 / macOS
  • Language: Python 3.10+
  • IDE: Visual Studio Code (VS Code)
  • Terminal: PowerShell (Windows) / zsh (macOS)

2. Problem

After the initial FastAPI setup, connecting a React frontend usually hits two configuration issues:

  1. The browser console shows a CORS (Cross-Origin Resource Sharing) error and blocks all data traffic.
  2. As the project grows, every new endpoint lands in a single main.py until the file is thousands of lines and impossible to maintain.

3. Cause

Why CORS fails

Modern browsers enforce the Same-Origin Policy (SOP). If React runs on localhost:3000 (or Vite?’s 5173) and FastAPI on localhost:8000, the ports differ - so the browser treats the call as cross-origin and blocks it unless the backend explicitly allows it.

Limits of a single-file API

Dumping all logic into one file only works for tiny toys. Without routers per domain, collaboration causes merge conflicts and finding endpoints becomes painful.

4. Solution

1) Structure endpoints with APIRouter

Create a routers folder and a module for city data:

mkdir routers
cd routers
touch city_data.py
cd ..

Write domain APIs in that file:

from fastapi import APIRouter

# Router instance (prefix + tag)
router = APIRouter(
    prefix="/api",
    tags=["City Data"],
)

@router.get("/locations")
def get_locations():
    # In production, read from a DB or GeoJSON here
    return {
        "status": "success",
        "data": [
            {"id": 1, "name": "Seoul", "lat": 37.5665, "lng": 126.9780},
            {"id": 2, "name": "New York", "lat": 40.7128, "lng": -74.0060},
        ],
    }

(If you prefer the original prefix style prefix="/api/", keep a consistent slash so paths do not double up.)

2) Wire CORS and include the router in main.py

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

# Import the split router module
from routers import city_data

app = FastAPI()

# 1. CORS allow list (whitelist)
# On deploy, replace these with your real frontend origin(s).
origins = [
    "http://localhost:3000",  # CRA default
    "http://127.0.0.1:3000",
    "http://localhost:5173",  # Vite default (add if you use Vite)
    "http://127.0.0.1:5173",
]

# 2. CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],  # GET, POST, PUT, DELETE, |--
    allow_headers=["*"],
)

# 3. Mount the router
app.include_router(city_data.router)

@app.get("/")
def read_root():
    return {"message": "FastAPI Backend is running perfectly with CORS setup!"}

If the server is already running with reload, save to apply changes; otherwise start it again:

uvicorn main:app --reload

Open http://127.0.0.1:8000/docs. The endpoints from city_data.py should appear under the City Data tag in Swagger.

Related: Talk to a FastAPI backend from React

5. Summary

CORS opens the backend door only to your React origins. APIRouter modularization keeps the architecture clean even when communication logic grows to tens of thousands of lines.

Related posts