When I first started working with React JS for my frontend, I quickly ran into one of the most frustrating issues: CORS errors when making API requests with Axios. If you’ve ever seen red errors in your browser console screaming about “No ‘Access-Control-Allow-Origin’ header”, you know exactly what I’m talking about.
I’ll walk you through the exact problem I faced, the mistake in my code, and how I finally fixed it by configuring the server correctly.
The Problem
In one of my Redux actions, I was trying to make a simple login call to my backend running on localhost:8000
while my frontend was on localhost:3000
.
Here’s the code I started with:
export function UserLogin(values) {
var headers = {
'Access-Control-Allow-Origin': '*',
'Accept': 'application/json',
'Content-Type': 'application/json'
};
const request = axios.post('http://localhost:8000/login', headers, values)
.then(response => {
console.log(response);
})
.catch(error => {
if (error.response) {
console.log(error.response.data);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
});
return {
type: LOGIN_REQUEST_SUCCESS,
payload: request
};
}
But instead of logging me in, the browser threw this at me:
Failed to load http://localhost:8000/login:
Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://localhost:3000' is therefore not allowed access.
At first glance, I thought I could “fix” it by adding the Access-Control-Allow-Origin
header in my Axios request. Spoiler: that doesn’t work.
Why This Happen
After digging deeper, I realized two major issues:
- CORS headers belong to the server, not the client.
Browsers will ignore anyAccess-Control-Allow-Origin
header I try to sneak into my request. That header has to come from the server response. - My Axios arguments were out of order.
The correct method signature is:axios.post(url, data, config)
But I mistakenly passed my headers as the request body and my values as the config. No wonder the server wasn’t happy!
Fix the Axios Call
Here’s the corrected version of my Redux action:
export function UserLogin(values) {
const request = axios
.post(
'http://localhost:8000/login',
values, // request body
{
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
// withCredentials: true // uncomment if you need cookies/sessions
}
)
.then(response => {
console.log(response);
})
.catch(error => {
if (error.response) {
console.log('Server responded with non-2xx:', error.response.data);
} else if (error.request) {
console.log('No response received:', error.request);
} else {
console.log('Error setting up request:', error.message);
}
});
return {
type: LOGIN_REQUEST_SUCCESS,
payload: request
};
}
With this change, my request was properly formatted, but I still had to solve the real issue: the backend server’s CORS configuration.
The Real Fix Configuring the Server
Because I was making requests from http://localhost:3000
to http://localhost:8000
, the browser sent a preflight OPTIONS request. Unless my server responded with the right CORS headers, the request was going to fail.
Node/Express Example
Here’s what I added to my Express backend:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(express.json());
app.use(
cors({
origin: 'http://localhost:3000',
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Accept', 'Authorization'],
credentials: true
})
);
app.options('*', cors()); // handle preflight requests
app.post('/login', (req, res) => {
res.json({ ok: true });
});
app.listen(8000, () => console.log('API running on port 8000'));
That was it! The moment I restarted my server, my React app could finally talk to my backend without complaints.
Other Backend
If you’re not on Node/Express, the solution is similar:
- Django: Install
django-cors-headers
and addCORS_ALLOWED_ORIGINS = ['http://localhost:3000']
- Laravel: Use the built-in
fruitcake/laravel-cors
package and configureconfig/cors.php
. - Spring Boot: Add
@CrossOrigin(origins = "http://localhost:3000")
to your controller or configure a globalCorsConfiguration
.
Common Gotchas I Learn the Hard Way
- If you’re sending cookies or Authorization headers:
- Set
withCredentials: true
in Axios. - Add
Access-Control-Allow-Credentials: true
on the server. - You cannot use
*
as the origin you must set a specific one likehttp://localhost:3000
.
- Set
- Using
Content-Type: application/json
automatically triggers a preflight OPTIONS request. That’s not a bug it’s how the browser works. Just make sure your server responds witth:
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET,POST,PUT,PATCH,DELETE,OPTIONS
Access-Control-Allow-Headers: Content-Type, Accept, Authorization
Final Thought
Running into CORS issues with React and Axios was frustrating until I realized the fix wasn’t on the frontend but the backend. I learned to structure Axios requests correctly (url, data, config)
and stopped trying to send CORS headers from the client. Once I configured my server to handle preflight requests and return the right headers, the errors vanished, and now I know to always check the server first when I see a No ‘Access-Control-Allow-Origin’ error.