🌉 API Integration: Connecting React to Express
For the first 3 phases of this curriculum, our Frontend and Backend lived in total isolation. React rendered fake, hardcoded arrays of data. Express created raw JSON and spat it into the void.
Fullstack Synergy is the art of connecting these two systems across the internet. We must build a bridge that safely, efficiently, and asynchronously transports data from PostgreSQL, through Node.js, over the Atlantic Ocean, and into React's DOM.
1️⃣ The Tools of the Trade: Fetch vs Axios
To make an HTTP Request from a browser, we need a client.
The Native fetch() API
Built directly into the browser. Requires absolutely zero installation.
The downside: It requires manual parsing of JSON, and it infuriatingly does not throw a Catch error if the server returns a 404 Not Found or 500 Server Error.
try {
const response = await fetch('http://localhost:5000/api/users');
// We must manually throw the error on 400/500 status codes!
if (!response.ok) throw new Error("Server rejected our request.");
// We must manually parse the text chunk into a JavaScript Object
const data = await response.json();
console.log(data);
} catch (error) {
console.log("Network failure", error);
}
The Industry Standard: axios
Axios is a third-party NPM package used by 90% of enterprises. It automatically parses JSON, automatically throws errors on 400/500 codes, and allows us to build powerful "Interceptors".
npm install axios
import axios from 'axios';
try {
// Axios handles the JSON parsing and Error throwing under the hood!
const response = await axios.get('http://localhost:5000/api/users');
// The data is instantly ready to use inside the 'data' property
console.log(response.data);
} catch (error) {
// Axios instantly catches the 404/500 and triggers this block.
console.log("Server responded with error:", error.response.status);
}
2️⃣ Building an API Service Class
If you write axios.get('http://localhost:5000/api/users') directly inside 15 different React components, you have created a nightmare.
What happens when you deploy to Production? The URL is no longer localhost:5000, it is https://api.mywebsite.com. You would have to manually find & replace 15 different files.
Professional Architecture dedicates a completely separate Service File just for API calls.
// src/services/apiService.js
import axios from 'axios';
// 1. Create a master Axios Instance. We define the Base URL exactly once.
const API = axios.create({
baseURL: process.env.REACT_APP_API_URL || 'http://localhost:5000/api'
});
// 2. Export isolated math functions that React can consume
export const UserAPI = {
// GET request
fetchAll: async () => {
const res = await API.get('/users');
return res.data;
},
// POST request passing a payload body
create: async (userData) => {
const res = await API.post('/users', userData);
return res.data;
}
};
3️⃣ Consuming the API inside React (useEffect)
Now that the Service is built, we import it inside a Component's Effect hook.
import { useState, useEffect } from 'react';
import { UserAPI } from '../services/apiService';
function UserList() {
const [users, setUsers] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const loadSystem = async () => {
try {
// Instantly call our clean API layer!
const data = await UserAPI.fetchAll();
setUsers(data);
} catch (err) {
setError("Failed to boot up systems. Check your Wi-Fi.");
} finally {
// Execution hits here regardless of success or failure
setIsLoading(false);
}
};
loadSystem();
}, []); // Empty array! We only fetch once on component mount.
// Conditional Rendering Steps
if (isLoading) return <div className="spinner">Loading Data...</div>;
if (error) return <div className="text-red-500 font-bold">{error}</div>;
return (
<ul>
{users.map(u => <li key={u.id}>{u.name}</li>)}
</ul>
);
}
4️⃣ The Next Level: React Query (TanStack)
Manually wiring isLoading, isError, and data state variables in every single component is tedious.
Furthermore, useEffect lacks caching. If a user clicks "Home", then "About", then clicks "Home" again, standard React forces the browser to re-download the exact same data from the server.
The enterprise solution to this is React Query. It handles all loading states automatically, caches data in memory, and prevents redundant network requests.
npm install @tanstack/react-query
import { useQuery } from '@tanstack/react-query';
import { UserAPI } from '../services/apiService';
function AdvancedUserList() {
// Look at how little code this takes!
// React Query manages the state perfectly behind the scenes.
const { data: users, isLoading, isError } = useQuery({
queryKey: ['systemUsers'], // The unique ID for the Ram Cache
queryFn: UserAPI.fetchAll // The function to execute if the Cache is empty
});
if (isLoading) return <p>Loading...</p>;
if (isError) return <p>Crash!</p>;
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
💡 Summary Hierarchy
| Tool | Purpose | Real-World Status |
|---|---|---|
fetch() | Native browser network tool. | Good for quick scripts, tedious for large apps. |
| Axios | 3rd party networking library. | The industry standard for sending HTTP. |
| API Class | Isolating URL strings out of UI. | Mandatory architecture rule. |
| React Query | Handles loading, error, and caching state. | The modern enterprise standard replacing custom useEffects. |