Errors need boundaries

The UI should not know every backend exception. The backend should not leak raw database errors. A useful application maps low-level failures into stable error shapes at boundaries.

Think in layers: validation errors help users correct input, network errors explain temporary failure, server errors need safe public messages and useful internal logs.

ts
type ApiError = {
  code: "VALIDATION" | "NETWORK" | "SERVER";
  message: string;
  fieldErrors?: Record<string, string>;
  requestId?: string;
};

fetch does not throw for HTTP errors

The fetch API throws for network failures, not for 400 or 500 responses. You need to check response.ok and parse the response body intentionally.

This difference matters because a 422 validation response is not the same as a dropped connection.

ts
const response = await fetch("/api/profile", options);

if (!response.ok) {
  const error = await response.json();
  throw normalizeApiError(error, response.status);
}

Try the idea

API error response demoValidation error
User message

Please enter a valid email address.

Developer log

422 VALIDATION: email format failed

User-facing and developer-facing errors are different

Users need a clear next action. Developers need enough context to debug. Mixing those two creates either vague logs or scary UI messages.

A safe error boundary can show a short message, include a request ID, and log the actual details server-side.

Error handling checklist

Use this checklist before a form, API flow, or dashboard action ships.

Production notes for JavaScript apps

When this concept moves from a tutorial into a real app, the important question is not whether the example works once. It is whether the behavior is still predictable when network latency, retries, user interaction, rendering, and logging all happen together.

Keep the boundary visible. Name the queue, state, error, or ownership rule in the code so the next developer can understand why the behavior exists without reading the whole feature from top to bottom.

Conclusion

The goal is not catching every error everywhere. The goal is making every important boundary predictable.