alan2207/bulletproof-react · error · Error
${message}
Error message
${message} What it means
This is the api client's fetchApi wrapper in apps/nextjs-app/src/lib/api-client.ts:95 firing on any non-2xx response. It first surfaces the server's message via the notifications store (browser only), then throws a plain Error whose message is `response.message` from the JSON body or `response.statusText` as fallback. All calls through `api.get/post/put/patch/delete` reject this way, so callers must catch it to handle API failures.
Source
Thrown at apps/nextjs-app/src/lib/api-client.ts:95
...headers,
...(cookieHeader ? { Cookie: cookieHeader } : {}),
},
body: body ? JSON.stringify(body) : undefined,
credentials: 'include',
cache,
next,
});
if (!response.ok) {
const message = (await response.json()).message || response.statusText;
if (typeof window !== 'undefined') {
useNotifications.getState().addNotification({
type: 'error',
title: 'Error',
message,
});
}
throw new Error(message);
}
return response.json();
}
export const api = {
get<T>(url: string, options?: RequestOptions): Promise<T> {
return fetchApi<T>(url, { ...options, method: 'GET' });
},
post<T>(url: string, body?: any, options?: RequestOptions): Promise<T> {
return fetchApi<T>(url, { ...options, method: 'POST', body });
},
put<T>(url: string, body?: any, options?: RequestOptions): Promise<T> {
return fetchApi<T>(url, { ...options, method: 'PUT', body });
},
patch<T>(url: string, body?: any, options?: RequestOptions): Promise<T> {
return fetchApi<T>(url, { ...options, method: 'PATCH', body });
},View on GitHub (pinned to 9506629ed0)
Solutions
- Read error.message — it mirrors the server's response message — and fix the underlying request (auth, payload, URL) that produced the non-2xx status.
- If it's a 401, your session expired: refresh/re-authenticate and retry; the API client relies on HttpOnly cookies, so check that credentials:'include' requests actually carry them.
- Verify API_URL in apps/nextjs-app/.env.local points to a running backend (or set NEXT_PUBLIC_ENABLE_API_MOCKING=true to use MSW mocks).
- In React Query consumers, handle the rejection via the mutation/query error callback or an ErrorBoundary instead of letting it bubble uncaught.
Example fix
// before
await api.post('/discussions', { body: payload }); // uncaught Error on 4xx/5xx
// after
try {
const discussion = await api.post('/discussions', { body: payload });
} catch (e) {
// message is the server-provided message or statusText
console.error((e as Error).message);
} Defensive patterns
Strategy: try-catch
Validate before calling
// Validate payload with Zod before calling the API to avoid 400s
import { Schema } from './schema';
const parsed = Schema.safeParse(payload);
if (!parsed.success) {
// surface form errors instead of hitting the API
console.error(parsed.error.flatten().fieldErrors);
} else {
await api.post('/discussions', { body: parsed.data });
} Type guard
import { ZodError } from 'zod';
const isApiError = (e: unknown): e is Error & { message: string } =>
e instanceof Error && e.message.length > 0; Try / catch
try {
const data = await api.get('/discussions');
} catch (error) {
// message mirrors the server response message (or statusText)
showToast((error as Error).message);
// optionally inspect status if you extend fetchApi to attach it
} Prevention
- Wrap every api.* call in React Query mutations/queries and handle errors via onError callbacks, not bare awaits.
- Keep client-side Zod schemas in sync with server schemas to prevent 400 validation responses.
- Verify API_URL and backend health before debugging deep call stacks — most blanket failures are a wrong base URL or dead server.
- Attach response.status to the thrown Error in fetchApi so callers can branch on 401 vs 404 vs 500.
When it happens
Trigger: Any request to `${env.API_URL}<url>` returning a non-ok status: 401 from an expired/missing auth cookie, 400 from Zod validation on the server, 404 for a missing resource, or 500 from a thrown server error. Also occurs when API_URL points to the wrong host/backend so every request 404s/502s, or when the mock API (MSW) is disabled but no real backend is running.
Common situations: Sitting idle until the auth cookie/JWT expires and then every mutation 401s; the API server not running locally while ENABLE_API_MOCKING=false; a misconfigured API_URL in .env.local pointing at a stale deployment; server-side Zod schema stricter than client form validation causing 400s.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
AI-assisted analysis of alan2207/bulletproof-react@9506629ed0 (2026-08-27).
Data as JSON: /api/errors/aad3a20f4830b480.
Report an issue: GitHub.