React Organizing API Calls
Table of Contents + −
In the last lesson, React Organizing Hooks, you gave your hooks a tidy home. This lesson does the same for the code that talks to your backend, so you organize API calls into one clean service layer instead of scattering raw fetch everywhere.
🤔 Why organize API calls?
Here’s the pain. Raw fetch calls spread through your components cause real problems.
- The same base URL is written in many components, so changing it means editing them all.
- The fetching, JSON parsing, and error handling get copied into every component.
- When the API changes, you have to hunt down every place that calls it.
- Components get cluttered with network details instead of focusing on the UI.
So the fix is simple. Put all that network logic in one place, and let components call simple functions.
🗂️ The service layer idea
The core idea is a service layer: a set of files whose only job is talking to the API. Here’s how it works.
- You make an
api/(orservices/) folder for these files. - Each file holds the functions for one area, like
userApi.jsfor user calls. - A component calls
getUsers()instead of writing the rawfetch.
Think of it as a clean boundary. On one side, components that show data. On the other, functions that know how to fetch it.
🧩 A central place for the base URL
Start by putting the base URL and shared fetch logic in one spot, so it’s never repeated.
const BASE_URL = "https://api.example.com";
export async function request(path) { const res = await fetch(`${BASE_URL}${path}`); if (!res.ok) { throw new Error(`Request failed: ${res.status}`); } return res.json();}Read what this small helper gives you.
BASE_URLlives in one place, so you change the server once and every call follows.requesthandles the common work: building the URL, checkingres.ok, and parsing JSON.- Every API function can reuse
requestinstead of repeating that logic.
📄 Group calls by area
Next, make a file per area of your API, with one function per call. These use the shared request.
import { request } from "./client";
export function getUsers() { return request("/users");}
export function getUser(id) { return request(`/users/${id}`);}See how clean each function is.
getUsersandgetUsereach describe one call, with a clear name.- They reuse
request, so they don’t repeat the URL or error handling. - All user-related calls live together in
userApi.js, easy to find.
So the component never sees a raw fetch. It just calls getUsers() or getUser(id), which read like plain English.
🖥️ Using the service from a component
Now the component is clean. It imports the function and calls it, with no network details.
import { useState, useEffect } from "react";import { getUsers } from "../api/userApi";
function UserList() { const [users, setUsers] = useState([]);
useEffect(() => { getUsers().then(setUsers).catch(console.error); }, []);
return ( <ul> {users.map((u) => ( <li key={u.id}>{u.name}</li> ))} </ul> );}Notice what the component does and doesn’t do.
- It calls
getUsers(), a clear function, instead of writingfetchwith a full URL. - It doesn’t know or care about the base URL, the JSON parsing, or the status check.
- If the API URL changes, this component doesn’t change at all; only
client.jsdoes.
So the component focuses on the UI, and all the network logic stays in the service layer. That’s the whole win.
Pairs well with custom hooks
You can go one step further and wrap these service calls in a custom hook, like useUsers(), which calls getUsers() and manages loading and error state. The service layer fetches; the hook adds the React state around it. Clean layers, each with one job.
⚠️ Common Mistakes
The biggest mistake is repeating the full URL and fetch logic in every component.
// ❌ raw fetch with the full URL, repeated in many componentsfetch("https://api.example.com/users").then((r) => r.json());
// ✅ call a clean service function; details live in one placeimport { getUsers } from "../api/userApi";getUsers();Keep these in mind.
- Don’t repeat the base URL across components. Keep it in one central file.
- Don’t put raw
fetchand error handling in components. Move it to the service layer. - Don’t mix unrelated calls in one file; group them by area, like
userApi,postApi.
✅ Best Practices
A few habits for organizing API calls.
- Put all API logic in a dedicated
api/(orservices/) folder. - Keep the base URL and shared fetch/error logic in one central helper.
- Group calls by area, one file per area, one clear function per call.
- Have components call those functions, not raw
fetch; optionally wrap them in custom hooks.
Easier to change and test
A service layer makes change easy: swap the base URL, add a header, or change error handling in one place. It also makes testing simpler, because you can test or mock the service functions on their own, away from the UI.
🧩 What You’ve Learned
- ✅ Scattered raw
fetchcalls with repeated URLs make an app fragile and hard to change - ✅ A service layer puts all API logic in one place, so components call clean functions
- ✅ Keep the base URL and shared fetch/error logic in one central helper
- ✅ Group calls by area (one file per area) with one clear function per call
- ✅ Components call functions like
getUsers(), never rawfetchwith full URLs - ✅ This makes the API easy to change and test, and keeps components focused on the UI
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What is a service layer for API calls?
Why: The service layer holds all the network logic. Components import simple functions like getUsers() instead of writing raw fetch calls.
- 2
Why keep the base URL in one central place?
Why: If the base URL is repeated everywhere, changing it is a hunt across many files. In one helper, you change it once and every call follows.
- 3
How should you group API call functions?
Why: Group related calls by area into their own file, with a clearly named function per call. This keeps related calls together and easy to find.
- 4
What should a component do instead of writing a raw fetch?
Why: The component calls a service function and stays focused on the UI. The base URL, parsing, and error handling all live in the service layer.
🚀 What’s Next?
Now your API calls are tidy and centralized. That base URL and any secret keys shouldn’t be hardcoded, though. Next you’ll learn to use environment variables to keep configuration out of your code.