The Basic Fetch Pattern

fetch('/api/users').then(res => res.json()).then(data => ...) is the minimal pattern. But res.json() throws on invalid JSON and res.ok doesn't throw on HTTP error status codes. Always check res.ok before calling res.json() and handle both network errors and HTTP error responses explicitly.

Async/Await for Clarity

The async/await equivalent is cleaner for complex flows: const res = await fetch(url); if (!res.ok) throw new Error(await res.text()); const data = await res.json(). Wrap in try-catch for error handling.

Type Safety with Validation

In TypeScript, casting the API response to a type provides editor support but no runtime guarantee. Use Zod, io-ts, or similar to parse and validate: const data = UserListSchema.parse(await res.json()). This throws a descriptive validation error if the API returns unexpected data.

Handling Pagination

Paginated API responses require multiple fetch calls. Check for a 'next' link or cursor in each response and continue fetching until none is present. Implement rate limiting to avoid hitting API rate limits.

Key Takeaway

Robust API JSON consumption requires explicit HTTP error checking, JSON parse error handling, and runtime type validation. These practices turn cryptic failures into clear, actionable errors.