How to Fix CORS Policy Errors in FastAPI with a React Frontend

By Connect Digital World | Web Development Made Easy | Last updated: October 2026
You built a FastAPI backend. You built a React frontend. Everything works when you test the API in your browser or in Postman. But the moment React calls your API, the browser console shows a big red message:
Access to fetch at 'http://localhost:8000/items' from origin
'http://localhost:3000' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.
Do not panic. This is one of the most common problems for full-stack developers, and the fix is short. Here it is first, and then we will explain it in simple words.
The Quick Fix: Copy and Paste This Code
Add this to your FastAPI main.py. It is about ten lines of code:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Now restart your server:
uvicorn main:app --reload
Refresh your React app. In most cases, the error is gone. Here is what each line does:
allow_origins=["http://localhost:3000"]: Allows requests from your React app. Write the exact address of your frontend.allow_credentials=True: Allows cookies and login information to be sent with requests.allow_methods=["*"]: Allows GET, POST, PUT, DELETE and other request types.allow_headers=["*"]: Allows headers such asAuthorizationandContent-Type.
Important: if you use allow_credentials=True, do not use allow_origins=["*"]. You must list your real origins. We explain why below.
What Is CORS? (Simple Explanation)
CORS stands for Cross-Origin Resource Sharing. Big words, simple idea.
Imagine your school has a rule: students from your class can walk into your classroom freely, but visitors from other classes must be on a list. Your browser is the school guard. It protects you from random websites reading private information from other websites.
What Is an Origin?
An origin is the combination of three things:
- The protocol (
httporhttps) - The domain (
localhost,example.com) - The port (
3000,8000)
If any one of these three is different, it is a different origin. So:
| Your React app | Your API | Same origin? |
|---|---|---|
| http://localhost:3000 | http://localhost:8000 | No (different port) |
| http://localhost:3000 | http://127.0.0.1:3000 | No (different domain name) |
| http://example.com | https://example.com | No (different protocol) |
| https://example.com | https://example.com | Yes |
When React on port 3000 calls FastAPI on port 8000, they are different origins. The browser says, “Wait, I need permission from the API first.” If the API does not send the right permission headers, the browser blocks the response. That is the CORS error.
Important: The Error Is Not From FastAPI Blocking You
Your API usually received the request and answered it. It is the browser that refuses to show the answer to your JavaScript. That is why Postman and curl work fine, because they do not enforce CORS. Only browsers do.
Step-by-Step: The Full Setup
Step 1: Install FastAPI and Uvicorn
pip install fastapi uvicorn
Step 2: Create the Backend (main.py)
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = [
"http://localhost:3000",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/items")
def read_items():
return [{"id": 1, "name": "Pencil"}, {"id": 2, "name": "Notebook"}]
Step 3: Run the Backend
uvicorn main:app --reload --port 8000
Step 4: Call the API From React
A simple React component using fetch:
import { useEffect, useState } from "react";
export default function Items() {
const [items, setItems] = useState([]);
useEffect(() => {
fetch("http://localhost:8000/items", {
credentials: "include", // sends cookies, needs allow_credentials=True
})
.then((res) => res.json())
.then((data) => setItems(data))
.catch((err) => console.error(err));
}, []);
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
If you use Axios instead of fetch, the setting is called withCredentials:
import axios from "axios";
axios.get("http://localhost:8000/items", { withCredentials: true })
.then((res) => console.log(res.data));
You only need credentials: "include" or withCredentials: true if you send cookies or login sessions. If you do not need them, you can leave them out and set allow_credentials=False (the default).
Why You Cannot Use “*” With Credentials
You may be tempted to write this:
allow_origins=["*"],
allow_credentials=True, # this combination is a bad idea
The * means “everyone is allowed.” Browser rules say that when you send cookies or login information, you must name the exact websites you trust. Otherwise any website on the internet could use a visitor’s login to talk to your API. FastAPI’s own documentation says that for credentials to work, the allowed origins must be specified explicitly and cannot be set to the wildcard.
The rule: If you use credentials, list your real frontend addresses. If you do not need credentials, ["*"] is okay for quick tests, but it is not a good idea for a real production app.
What Is a Preflight Request?
Sometimes the browser sends a “test knock” before the real request. This is called a preflight request, and it uses the OPTIONS method. The browser does this when your request is “not simple”, for example when you:
- Use
PUT,PATCHorDELETE. - Send JSON with
Content-Type: application/json. - Add custom headers, such as
Authorization: Bearer ....
The browser asks the API, “Am I allowed to do this?” The API must answer with the right headers. The good news is that CORSMiddleware handles preflight requests for you automatically, as long as the origin, method and headers you use are allowed.
A Safer Setup for Production
For a real website, do not hardcode localhost. Use an environment variable so you can change origins without editing code:
import os
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = os.getenv("ALLOWED_ORIGINS", "http://localhost:3000").split(",")
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Authorization", "Content-Type"],
)
Then, on your server, set:
ALLOWED_ORIGINS=https://myapp.com,https://www.myapp.com
Being specific about methods and headers is safer than using "*", and it also avoids surprises with credentials.
The 9 Most Common CORS Mistakes (and Fixes)
1. A Trailing Slash in the Origin
Wrong: "http://localhost:3000/"
Right: "http://localhost:3000"
Origins do not end with a slash. This tiny difference causes lots of confusion.
2. Wrong Port (Vite vs Create React App)
Create React App usually runs on port 3000, but Vite runs on port 5173 by default. If you use Vite, change the origin:
allow_origins=["http://localhost:5173"]
Always copy the exact address from your browser’s address bar.
3. localhost vs 127.0.0.1
To the browser, localhost and 127.0.0.1 are different origins. If you open your app at http://127.0.0.1:3000, add that too:
allow_origins=["http://localhost:3000", "http://127.0.0.1:3000"]
4. http vs https
If your frontend is on https://myapp.com, the origin must start with https. Do not mix them up.
5. Middleware Added in the Wrong Place
Add CORSMiddleware right after creating the app, before your routes and other custom middleware. If you have other middleware, make sure CORS is not accidentally blocked by them.
6. A Server Error Hiding as a CORS Error
This one tricks many developers. If your FastAPI code crashes and returns a 500 error, the error response may not include CORS headers, so the browser shows a CORS message even though the real problem is a bug in your code. Open the browser’s Network tab, click the failed request and check the status code. Also look at your FastAPI terminal for a traceback.
7. Redirects From Missing Trailing Slashes
If React calls /items but your route is /items/, FastAPI may redirect the request. In some cases, the redirected request fails with a CORS error. Make the URLs match exactly in React and FastAPI.
8. Forgetting credentials in React
If your API uses cookies, you need allow_credentials=True in FastAPI and credentials: "include" (or withCredentials: true) in React. If only one side is set, login cookies will not work.
9. Cookie Settings Block Your Login
If you use cookies for login, their settings (SameSite, Secure) matter, especially when your frontend and backend are on different domains in production. A cookie that works on localhost may not work between two different websites unless it is set up correctly. This is a cookie rule, not a CORS rule, but it often appears at the same time.
How to Debug CORS Like a Pro
1. Look at the Network Tab
- Open your browser’s developer tools (F12).
- Go to the Network tab and click the failed request.
- Look at the Response Headers. You should see
access-control-allow-originwith your frontend’s address. - Also look for a separate
OPTIONSrequest, which is the preflight.
2. Test the Preflight With curl
This command pretends to be the browser’s preflight request:
curl -i -X OPTIONS http://localhost:8000/items \
-H "Origin: http://localhost:3000" \
-H "Access-Control-Request-Method: GET"
If CORS is set up right, the response includes lines such as access-control-allow-origin: http://localhost:3000. If those lines are missing, your origin is probably not in the allowed list, or the middleware is not active.
3. Restart the Server
After changing the code, restart Uvicorn. With --reload it usually restarts by itself, but check the terminal to be sure.
4. Clear the Cache
Browsers can cache preflight answers. Do a hard refresh (Ctrl + Shift + R) or test in a private window.
Another Option: Use a Dev Proxy Instead
During development, you can avoid CORS altogether by letting your React dev server forward API calls to FastAPI. The browser then thinks everything comes from one origin.
Vite: add a proxy in vite.config.js:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
proxy: {
"/api": "http://localhost:8000",
},
},
});
Then in React, call fetch("/api/items") instead of the full address. Your FastAPI routes should then be available under /api, or you can add a rewrite rule in the proxy settings.
Create React App: add this line to package.json:
"proxy": "http://localhost:8000"
A proxy is handy for local work, but you still need correct CORS or a similar setup in production if your frontend and API live on different domains.
Is It Safe to Allow All Origins?
For learning, a quick test project or a public API that has no private data, allow_origins=["*"] without credentials can be fine. For anything with logins, user data or payments, it is risky. Always list the specific frontends you trust. CORS is not a replacement for real security, such as proper authentication and permission checks, but it is an important layer.
Simple Word List (Glossary)
- CORS: Cross-Origin Resource Sharing, the browser rule for sharing data between different origins.
- Origin: The combination of protocol, domain and port.
- Middleware: Code that runs on every request before it reaches your route.
- Preflight: A test request (OPTIONS) the browser sends before the real request.
- Credentials: Cookies or login data sent with a request.
- Header: Extra information attached to a request or response.
- Wildcard (*): A symbol that means “everything”.
- Proxy: A middleman that forwards requests from one address to another.
- FastAPI: A fast Python framework for building APIs.
- Uvicorn: A server that runs FastAPI apps.
Frequently Asked Questions (FAQ)
How do I fix CORS errors in FastAPI?
Add CORSMiddleware to your app and list your frontend origin, for example http://localhost:3000, in allow_origins. Then restart your server.
Why do I get a CORS error in React but not in Postman?
Browsers enforce CORS rules, but Postman and curl do not. The request reaches your API in both cases, but the browser blocks the response from your JavaScript if the headers are missing.
Can I use allow_origins=[“*”] with allow_credentials=True?
No. When credentials are enabled, you must list specific origins instead of a wildcard.
Why does my CORS setup work on localhost but not in production?
You probably forgot to add your production domain to the allowed origins, or you are mixing http and https, or your cookies are not set up for cross-site use.
Do I need CORS if I use a proxy?
Not in local development, because the browser sees one origin. In production you may still need CORS if your frontend and API are on different domains.
What port does Vite use?
Vite uses port 5173 by default. Create React App usually uses 3000. Always use the address shown in your browser.
Why do I still get a CORS error after adding the middleware?
Check for a typo in the origin, a trailing slash, the wrong port, a crash causing a 500 error, a redirect or a server that was not restarted.
Is CORS a security feature for my API?
It protects users in browsers, but it does not replace authentication. Anyone can still call your API with tools like curl, so always protect private endpoints properly.
Final Thoughts
CORS errors are annoying, but they are not mysterious. The browser is just asking your API for permission, and CORSMiddleware is how FastAPI says “yes”. Remember the four key pieces: the exact origin, allow_credentials=True only with specific origins, matching settings in React, and a quick look at the Network tab when something goes wrong.
Copy the ten lines at the top of this guide, change the origin if you use Vite, and you should be back to building your app in minutes.
Did this fix your CORS problem? Tell us in the comments which mistake it was. For more easy coding guides, keep following Connect Digital World.
Sources and further reading: The official FastAPI documentation on CORS, Starlette’s CORSMiddleware documentation, MDN Web Docs on Cross-Origin Resource Sharing, and the Vite and Create React App proxy documentation.



