API Integration with Fetch/Axios
Part of the Full Stack Developer Roadmap
In short
Till now your React app showed fake data from an array. The moment it calls your real Express API and shows real rows from the database, you are officially doing full stack.
Actually wiring your React components up to real backend endpoints instead of mock data — the moment your app becomes genuinely full-stack.

Think of it like this
The chit at the mess window
In the hostel mess you don't walk into the kitchen. You push a chit through the window saying 'one dal rice', and a plate comes back. Sometimes the reply is 'dal over, come tomorrow'. fetch is your chit, the API is the window, and you must handle both kinds of reply.
Watch it happen
One list, three layers
1/6ProductList mounts and its effect fires once, asking for real data instead of a hardcoded array.
The big ideas
What you need to know
fetch is built in
Every modern browser and Node 18+ has fetch. It returns a Promise of a Response, and you call res.json() to read the body.
fetch doesn't throw on 404 or 500
It only rejects when the network fails. Check res.ok yourself, or a 500 error page gets treated as data.
Axios adds conveniences
Axios parses JSON, throws on 4xx and 5xx, and has interceptors for adding a token to every request. Nice, but optional.
Keep calls in one file
Put API calls in api.ts. Components call getProducts(), not raw URLs, so changing an endpoint means editing one place.
See it in code
Walk through the code
1type Product = { id: number; name: string; price: number };2// api.ts: the one place that talks to the backend3export async function getProducts(signal?: AbortSignal): Promise<Product[]> {4 const res = await fetch("/api/products", { signal });5 if (!res.ok) throw new Error(`GET /api/products failed: ${res.status}`);6 return res.json();7}8// Axios version: const { data } = await axios.get<Product[]>("/api/products", { signal });9// ProductList.tsx10useEffect(() => {11 const controller = new AbortController();12 getProducts(controller.signal).then(setProducts).catch((err) => {13 if (err.name !== "AbortError") setError(err.message);14 });15 return () => controller.abort();16}, []);1/4One function owns the URL and checks res.ok, because fetch happily resolves on a 404 or 500.
The relative URL /api/products works when a dev proxy or the same domain serves both halves. Otherwise build it from an environment variable.
Save yourself the pain
Common mistakes
Oops
Using the result of fetch without checking res.ok.
Do this instead
Check res.ok (or use Axios) so a 401 or 500 shows an error message instead of crashing on bad data.
Oops
Hardcoding http://localhost:4000 inside twenty components.
Do this instead
Build the URL in one API module from an environment variable, so production uses the real API address.
Oops
Never cancelling requests when the user moves to another page.
Do this instead
Pass an AbortController signal and abort in the effect cleanup, so stale replies don't overwrite fresh ones.
Your mission
Wire a list to your API
Quick check
Your Express API returns a 500 with an HTML error page. What does fetch do?
Go deeper
- 1Replace a component's mock data with a real `fetch` or Axios call to your backend.
- 2Set up an Axios instance with a base URL so you don't repeat it everywhere.
- 3Handle the request lifecycle — loading, success, and error — for every API call.
More topics at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.