Stop 09 · Connecting Frontend & BackendTopics · 2/8

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.

The blob character slips a paper chit through a mess window and gets a plate of rice and dal back.

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

React page
Express API
Postgres
useEffect runs

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

tsx
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

~30 min

Quick check

Your Express API returns a 500 with an HTML error page. What does fetch do?

Go deeper

  1. 1Replace a component's mock data with a real `fetch` or Axios call to your backend.
  2. 2Set up an Axios instance with a base URL so you don't repeat it everywhere.
  3. 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.

Download
InsideEdge