API Integration
Typed REST and GraphQL clients with interceptors, retry logic, and centralized error handling using React Query.
01
Principle
All API calls flow through a single typed client layer. React Query wraps the client for caching and synchronization. Components never call fetch() directly — they use custom hooks that compose the query client and API client together.
lightbulb
Use Zod to validate API responses at the boundary. This catches backend contract violations early and ensures your types are always accurate.
02
Rules
- check_circleSingle API Client InstanceOne Axios/fetch instance with all interceptors. Import it everywhere via a singleton.
- check_circleType All ResponsesDefine TypeScript interfaces for every endpoint. Use Zod for runtime validation.
- check_circleCentralized Error HandlingMap API error codes to user-friendly messages in one place — not scattered in components.
- check_circleRetry Transient FailuresConfigure React Query retry logic for 5xx errors. Never retry 4xx client errors.
03
Pattern
lib/api-client.ts
import axios from 'axios'; import { z } from 'zod'; const apiClient = axios.create({ baseURL: process.env.NEXT_PUBLIC_API_URL }); export async function fetchTyped<T>( url: string, schema: z.ZodType<T>, ): Promise<T> { const { data } = await apiClient.get(url); return schema.parse(data); }
04
Implementation
info
Version Compatibility
Requires React 19+ and the latest stable versions of all dependencies shown.
In Next.js, use Route Handlers as a BFF (Backend for Frontend) layer to proxy external APIs and add auth headers server-side.
app/api/posts/route.ts
import { NextResponse } from 'next/server'; import { postListSchema } from '@/lib/schemas'; export async function GET() { const res = await fetch(process.env.EXTERNAL_API + '/posts', { headers: { Authorization: `Bearer ${process.env.API_SECRET}` }, next: { revalidate: 60 }, }); const data = postListSchema.parse(await res.json()); return NextResponse.json(data); }