Building your first full stack application can feel overwhelming when you realize your React frontend and Node.js backend are two separate worlds that need to talk to each other. If you’ve ever wondered how to connect React to a Node.js backend, you’re not alone. This is the exact wall most beginners hit after learning React and Node separately.
In this practical tutorial, we’ll walk through the complete connection pattern: setting up both projects, wiring an Express API, making HTTP requests with Axios, handling JSON responses, and configuring a proxy so you don’t run into CORS headaches during development.
Why React and Node.js Work So Well Together
React handles what users see in the browser, while Node.js with Express runs your server logic, talks to your database, and exposes API endpoints. They communicate over HTTP using JSON. Once you understand this pattern, you can build any full stack app you want.
Here’s the basic flow:
- Your React component triggers an event (a button click, a page load, a form submit).
- Axios sends an HTTP request to your Express server.
- Express processes the request and returns a JSON response.
- React updates its state with the data and re-renders the UI.

Step 1: Set Up the Project Structure
Create a parent folder that will hold both your client and server. This keeps everything organized.
mkdir fullstack-app
cd fullstack-app
mkdir client server
You’ll end up with this structure:
| Folder | Purpose |
|---|---|
| client | React frontend (Vite or Create React App) |
| server | Express backend with API routes |
Step 2: Build the Express Backend
Move into the server folder and initialize a Node project.
cd server
npm init -y
npm install express cors
Create a file called index.js inside the server folder:
const express = require('express');
const cors = require('cors');
const app = express();
const PORT = 5000;
app.use(cors());
app.use(express.json());
app.get('/api/users', (req, res) => {
res.json([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
]);
});
app.post('/api/users', (req, res) => {
const newUser = req.body;
res.status(201).json({ message: 'User created', user: newUser });
});
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
Start the server:
node index.js
Visit http://localhost:5000/api/users in your browser and you should see your JSON response.
Step 3: Set Up the React Frontend
From the parent folder, create your React app. Vite is the modern recommended choice in 2026.
cd ../client
npm create vite@latest . -- --template react
npm install
npm install axios
Why Axios Instead of fetch?
Both work, but Axios gives you some real advantages:
- Automatic JSON parsing of responses
- Cleaner error handling with proper status code rejection
- Request and response interceptors
- Easy timeout and base URL configuration
- Better browser compatibility for older targets

Step 4: Configure the Development Proxy
This is the part that trips up most beginners. Your React dev server runs on port 5173 (Vite) or 3000 (CRA), while your Express API runs on port 5000. Without a proxy, you’ll fight CORS issues or have to hardcode full URLs.
For Vite Users
Open vite.config.js and add a server proxy:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true
}
}
}
});
For Create React App Users
Add this single line to your package.json:
"proxy": "http://localhost:5000"
Now any request starting with /api from your React app will be forwarded to your Express server automatically. No CORS errors, no full URLs in your code.
Step 5: Make Your First Axios Request from React
Replace the contents of src/App.jsx with:
import { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
axios.get('/api/users')
.then(response => {
setUsers(response.data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<h1>Users</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
export default App;
Run your React app with npm run dev and you should see your user list rendered from the Express API.
Step 6: Sending Data with POST Requests
Reading data is only half the story. Here’s how to send data from a React form to your Node backend:
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await axios.post('/api/users', {
name: 'Charlie'
});
console.log('Created:', response.data);
} catch (err) {
console.error('Failed to create user:', err);
}
};
Axios automatically serializes the object to JSON and sets the correct Content-Type header. On the Express side, express.json() middleware parses the body so you can access it via req.body.

Common Mistakes to Avoid
- Forgetting express.json() middleware means req.body will be undefined on POST requests.
- Running both servers on the same port will cause one to fail. Keep them separate during development.
- Hardcoding localhost URLs in production breaks deployments. Use environment variables instead.
- Skipping the proxy setup forces you to enable CORS for every endpoint, which is fine for development but adds friction.
- Not handling errors in your Axios calls leaves users staring at broken UI when the network fails.
Going to Production
When you deploy, the proxy trick disappears. You have two main options:
- Serve React from Express: Build your React app with npm run build, then have Express serve the static files from the build folder. One server, one URL, no CORS.
- Deploy separately: Host React on a static platform (Vercel, Netlify) and Node on a separate service (Render, Railway, Fly.io). You’ll need to enable CORS properly and use environment variables for the API URL.
For option 1, add this to your Express server:
const path = require('path');
app.use(express.static(path.join(__dirname, '../client/dist')));
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../client/dist/index.html'));
});
FAQ
Do I need both React and Node.js running at the same time?
Yes, during development. React runs on its dev server (port 5173 or 3000) and Express runs on its own port (typically 5000). They communicate over HTTP through the proxy.
Can I use fetch instead of Axios?
Absolutely. The native fetch API works fine for simple cases. Axios just provides extra convenience features that become valuable as your app grows.
Is React still in demand in 2026?
Yes. React remains one of the most widely used frontend libraries, and full stack roles combining React with Node.js continue to be highly sought after by employers.
How do I handle authentication between React and Node?
The most common pattern is JWT tokens. The backend issues a token on login, the frontend stores it (usually in httpOnly cookies for security), and Axios sends it with every subsequent request via an Authorization header.
Why am I getting a CORS error?
Either your proxy isn’t configured correctly, or you’re calling the full backend URL directly. Make sure your Axios calls use relative paths like /api/users in development, and verify your proxy config is in place.
Should I put React and Node in the same repository?
For small to medium projects, a monorepo with client and server folders works great. For larger teams, separate repositories give you cleaner CI/CD pipelines and independent deployments.
Wrapping Up
Connecting React to a Node.js backend comes down to four pieces: an Express server exposing JSON endpoints, a React app making Axios requests, a dev proxy to avoid CORS issues, and proper error handling on both sides. Once you’ve wired these together once, you can replicate the pattern in every full stack project you build.
Start small with a single GET endpoint, confirm it works, then add POST, PUT, and DELETE as your app grows. The connection pattern stays exactly the same.

