Client Error Handling
On this page
Errors happen in layers
Client-side errors fall into three buckets: network failures, HTTP failures (non-2xx), and application/validation failures. Treat them differently to improve UX and reduce retries.
Network errors
- DNS issues, offline, timeouts, TLS problems
- Fetch rejects only for network-level errors
HTTP errors
- 4xx: client request issue (usually don't retry automatically)
- 5xx: server issue (retry may be reasonable)
- 429: rate limited (retry after delay)
Parse errors
Sometimes the response is not JSON (bad proxy, HTML error page). Your client should handle JSON parsing failures gracefully.
A robust fetch helper (pattern)
async function api(url, options = {}) {
const res = await fetch(url, {
headers: { "Accept": "application/json", ...(options.headers || {}) },
...options
});
const text = await res.text();
const json = text ? (JSON.parse(text) || null) : null;
if (!res.ok) {
const msg = json?.detail || json?.title || ("HTTP " + res.status);
const err = new Error(msg);
err.status = res.status;
err.body = json;
throw err;
}
return json;
}
Retries with backoff
Retry only when it makes sense (timeouts, 502/503, sometimes 429). Use exponential backoff with jitter to avoid thundering herds.
Example backoff strategy (concept)
delay = base * 2^attempt + random(0..jitter)
User-friendly error mapping
- 422: show field-level messages
- 401: trigger login refresh
- 403: show “not allowed”
- 500: show generic message + retry
Checklist
- Network errors are handled separately from HTTP errors.
- Non-2xx responses are parsed safely.
- Retries are limited and use backoff.