API integration is where frontend and backend contracts become user experience. A good integration layer hides transport details from UI components while keeping data types, errors, and loading states explicit.
Typed Fetch Wrappers
type ApiResult<T> = | { ok: true; data: T } | { ok: false; status: number; message: string };async function getJson<T>(path: string): Promise<ApiResult<T>> { const res = await fetch(path, { headers: { Accept: "application/json" }, credentials: "include", }); if (!res.ok) { return { ok: false, status: res.status, message: "Request failed" }; } return { ok: true, data: (await res.json()) as T };}
UI code should not repeat credentials, headers, base URLs, and error parsing in every component.
Data Shapes for Components
Backend models are often not the same as UI view models. Adapt them at the boundary.
Mapping API data into view models is not wasteful. It protects components from backend naming, nullable fields, and transport-specific details.
Further Learning
Search these terms to go deeper:
“frontend API client layer patterns” — structuring request code
“TypeScript API response types” — safer client contracts
“loading error empty states UI” — complete async interfaces
“OpenAPI generated TypeScript client” — generating clients from API specs
API Integration Patterns
Connecting your frontend to your backend API is more than just calling fetch. Good API integration means the messy parts of talking to a server (headers, errors, data shapes) live in one place, not scattered across every component.
Wrap fetch once, use it everywhere
Instead of writing the same fetch boilerplate in every component, write one small helper and reuse it:
This isn’t wasted effort — it protects your components from backend naming quirks (like snake_case) and keeps your UI code clean and readable.
Always plan for three states
Every piece of data from an API can be in one of three states, and your UI should handle all of them: loading (still fetching), empty (fetched, but nothing there), and error (something went wrong). Forgetting one of these is the most common source of “why is this component broken?” bugs.
In one sentence
Centralize your API calls in one reusable helper (consistent headers and error handling), convert backend data shapes into clean view models for your UI, and always design for loading/empty/error states.
Want to go deeper?
Switch to Expert mode above for typed ApiResult contracts and generating clients from OpenAPI specs.