API Integration
Frontend integration with backend APIs — REST, GraphQL, auth, error handling, caching
API Integration
How the frontend talks to the backend. This section covers the patterns that survive production: typed request/response, error normalization, auth/token rotation, caching, and the trade-offs between REST and GraphQL.
Topics
REST
Resource modeling, status codes, pagination, idempotency, OpenAPI codegen
GraphQL
Schema-first thinking, queries vs mutations, fragments, caching, N+1, Relay vs Apollo vs urql
Error Handling
Network vs HTTP vs domain errors, retries, circuit breaking, user-facing messaging
Auth & Tokens
JWT vs sessions, refresh strategies, mobile secure storage, OAuth/OIDC flows
Caching & Optimistic Updates
HTTP cache, TanStack Query, optimistic mutations, invalidation strategies
Pick the Style That Fits Your Backend
| Backend exposes | Frontend default |
|---|---|
| OpenAPI/Swagger REST | REST + generated client (orval, openapi-typescript) |
| GraphQL schema | GraphQL + codegen client (graphql-codegen) |
| gRPC | gRPC-web or Connect (Buf) — REST-style codegen still applies |
| Mixed | Use a thin per-domain layer to hide the difference |
There is no "winner" — both can ship. REST is operationally simpler and CDN-friendly; GraphQL is best when many clients consume the same backend and over-fetching is a real concern.
The Layer Sketch
UI Component
│
▼
useQuery / useMutation (TanStack Query, Apollo, urql, RTK Query)
│
▼
API client (typed wrapper around fetch / Axios / GraphQL transport)
│
▼
Network layer (HTTP/HTTPS, interceptors, auth, retries)
│
▼
BackendEach layer has one job:
- UI components describe what they need — no fetch, no parse.
- Query hooks own caching, retries, refetch semantics.
- API client owns the transport, types, and the runtime parse against the schema.
- Network layer owns auth headers, timeouts, telemetry.
Universal Principles
1. Parse At The Boundary
TypeScript types do not exist at runtime. Validate every payload with zod (or equivalent). A backend deploy that drops a field should fail loudly in the parser, not at a random call site three screens later.
2. Type The Contract
Generate types from the backend schema:
- OpenAPI →
openapi-typescript,orval,kubb - GraphQL →
graphql-codegen - gRPC / Connect →
buf generate
Hand-typing endpoints is a source of bugs and drift.
3. Idempotency Where It Matters
GET,PUT,DELETEare idempotent — safe to retry.POSTis not — only retry with an idempotency key the backend supports.- Mobile clients on flaky networks will retry; design for it.
4. Distinguish Error Kinds
| Kind | Example | Strategy |
|---|---|---|
| Network | DNS failure, timeout | Retry with backoff; show offline UI |
| HTTP 5xx | Backend bug, deploy | Retry; alert on rate |
| HTTP 4xx | 401 / 403 / 422 | Don't retry; map to UX |
| Domain | { error: "INSUFFICIENT_FUNDS" } | Domain-specific UX |
| Cancellation | AbortController | Silent; not an error |
Lumping them into "request failed" wastes information.
5. Cancel On Unmount
Always pass an AbortSignal. Without it, a fast tab switch can deliver responses to a screen that has gone away — and update state on an unmounted tree.
6. Don't Block Render On Data You Already Had
Show stale data while refetching. TanStack Query's staleTime + background refetch is the simplest way to do this right.
7. Treat Tokens Like Secrets
- Mobile: store in Keychain / Keystore (
react-native-keychain,flutter_secure_storage). - Web: HttpOnly + Secure + SameSite cookies if the backend can. Avoid
localStoragefor refresh tokens. - Refresh tokens never live in JS-readable storage if avoidable.
What Goes Wrong (And Why It's Here)
| Failure | Cause | Where covered |
|---|---|---|
| App keeps requesting forever after backend goes 500 | No retry caps, no backoff | error-handling |
| Cart count flickers between values | No optimistic update, stale cache | caching |
| Token refresh causes duplicate refresh calls | No mutex on the refresh path | auth-tokens |
| GraphQL fragment fields are missing on render | Fragment not requested by the parent query | graphql |
| Pagination duplicates rows | Cursor reset on filter change without cache invalidation | rest |
Each of the sub-pages is built around fixing one of these classes of issue.