Steven's Knowledge

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

Pick the Style That Fits Your Backend

Backend exposesFrontend default
OpenAPI/Swagger RESTREST + generated client (orval, openapi-typescript)
GraphQL schemaGraphQL + codegen client (graphql-codegen)
gRPCgRPC-web or Connect (Buf) — REST-style codegen still applies
MixedUse 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)


Backend

Each 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:

  • OpenAPIopenapi-typescript, orval, kubb
  • GraphQLgraphql-codegen
  • gRPC / Connectbuf generate

Hand-typing endpoints is a source of bugs and drift.

3. Idempotency Where It Matters

  • GET, PUT, DELETE are idempotent — safe to retry.
  • POST is not — only retry with an idempotency key the backend supports.
  • Mobile clients on flaky networks will retry; design for it.

4. Distinguish Error Kinds

KindExampleStrategy
NetworkDNS failure, timeoutRetry with backoff; show offline UI
HTTP 5xxBackend bug, deployRetry; alert on rate
HTTP 4xx401 / 403 / 422Don't retry; map to UX
Domain{ error: "INSUFFICIENT_FUNDS" }Domain-specific UX
CancellationAbortControllerSilent; 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 localStorage for refresh tokens.
  • Refresh tokens never live in JS-readable storage if avoidable.

What Goes Wrong (And Why It's Here)

FailureCauseWhere covered
App keeps requesting forever after backend goes 500No retry caps, no backofferror-handling
Cart count flickers between valuesNo optimistic update, stale cachecaching
Token refresh causes duplicate refresh callsNo mutex on the refresh pathauth-tokens
GraphQL fragment fields are missing on renderFragment not requested by the parent querygraphql
Pagination duplicates rowsCursor reset on filter change without cache invalidationrest

Each of the sub-pages is built around fixing one of these classes of issue.

On this page