top of page

Error Handling Across the Full Stack

  • Contributor
  • Sep 20, 2025
  • 5 min read

The previous posts in this path covered API design, authentication patterns, and state management. This post covers the concern that connects all of them: error handling — the design of what happens when something goes wrong, at every layer of the stack.

Errors are inevitable. Databases go down. APIs return unexpected responses. Users submit invalid data. Network connections drop. The question is not whether errors will occur — it is whether your application handles them gracefully or collapses into a blank screen, a cryptic error code, or an infinite loading spinner.

Good error handling serves two audiences simultaneously: users who need to understand what happened and what to do next, and developers who need to diagnose and fix the underlying problem. These audiences need different information, and the error handling system must serve both without leaking internal details to users or hiding diagnostic information from developers.

The Error Flow: Backend to Frontend

When an error occurs in the backend, it travels through multiple layers before reaching the user. Each layer has a responsibility.

The backend catches the error, logs diagnostic information (stack trace, request context, correlation ID), and returns a structured error response. The response includes an error code (machine-readable, for client-side handling), a human-readable message (for display to the user), and optionally, details about what went wrong and how to fix it.

The backend must never return internal details — stack traces, database names, file paths, or SQL queries — to the client. These details help attackers and confuse users. They belong in the server log, not in the response.

The API layer standardizes error responses. A consistent error format across all endpoints lets the frontend handle errors uniformly. A common format: {"error": {"code": "VALIDATION_ERROR", "message": "The email address is not valid", "details": [{"field": "email", "message": "Must be a valid email address"}]}}. The code enables programmatic handling. The message enables display. The details enable field-level error display.

HTTP status codes carry meaning. 400 means the client sent bad data (show validation errors). 401 means the client is not authenticated (redirect to login). 403 means the client is authenticated but not authorized (show a permission error). 404 means the resource does not exist (show a not-found page). 500 means the server failed (show a generic error with a support contact).

The frontend receives the error response and decides how to present it. The presentation depends on the error type: validation errors are shown next to the relevant form fields, authentication errors redirect to login, authorization errors show an access denied message, server errors show a generic error message with a way to report the issue.

Frontend Error Boundaries

Not all errors come from the backend. The frontend generates its own errors: JavaScript exceptions, rendering failures, state management bugs. These errors must be caught before they crash the entire application.

Error boundaries (in React) and global error handlers catch unhandled exceptions and display a fallback UI instead of a blank screen. The fallback UI should tell the user something went wrong, offer a way to recover (retry the action, refresh the page, navigate to a safe page), and provide a way to report the issue.

The granularity of error boundaries matters. A single error boundary around the entire application catches everything but replaces the whole page when one component fails. Multiple error boundaries around individual sections (sidebar, main content, widget) allow one section to fail while the rest of the application remains functional.

The practice: wrap independent sections of the UI in their own error boundaries. If the notification widget crashes, the user should still be able to access the main application. If a single card in a dashboard fails to render, the other cards should remain visible.

Retry and Recovery

Many errors are transient — a network timeout, a temporary service unavailability, a brief database overload. The user should not need to know about transient errors that the application can recover from automatically.

Automatic retry with backoff handles transient API failures. The first failure waits 1 second and retries. The second failure waits 2 seconds. The third failure gives up and shows the user an error. This pattern is built into most HTTP client libraries and server state libraries (React Query retries failed queries automatically by default).

Offline handling manages network disconnection gracefully. Instead of error messages for every failed request when the user is offline, the application shows a single "you are offline" indicator and queues operations for retry when connectivity returns. Service workers and background sync enable this pattern for progressive web applications.

The key principle: handle errors at the lowest level that can meaningfully address them. A network error in a data fetch should be retried by the fetching library, not propagated to the user. Only errors that cannot be resolved automatically should reach the user, and when they do, they should include actionable guidance.

User-Facing Error Messages

Error messages are a UX surface. Bad error messages frustrate users and generate support tickets. Good error messages help users fix the problem themselves.

Bad: "Error 500: Internal Server Error." This tells the user nothing actionable.

Bad: "An error occurred. Please try again later." This is only slightly better — the user does not know what failed, whether their data was saved, or whether trying again will help.

Good: "We could not save your changes because the server is temporarily unavailable. Your changes have been saved locally and will be synchronized when the connection is restored." This tells the user what happened, whether their data is safe, and what will happen next.

The pattern for user-facing error messages: what happened (in plain language), what the impact is (was data lost? is the action incomplete?), and what the user can do (retry, contact support, wait). Skip the technical details — users do not need to know that the database connection pool was exhausted.

Logging for Developers

While users see friendly messages, developers need diagnostic details. Every error should be logged with enough context to reproduce and fix the issue.

The logging context includes: a correlation ID (linking all log entries for a single request), the request details (endpoint, parameters, headers — excluding sensitive data), the error details (exception type, message, stack trace), the application state at the time of the error (relevant variable values, feature flags), and the user context (user ID, not PII).

The correlation ID is the most important piece. When a user reports "I got an error," the support team asks for the correlation ID (displayed in the error message or available in browser dev tools), and the developer can find every log entry associated with that specific request — across all services, from the API gateway through the backend to the database.

The Takeaway

Error handling is a full-stack concern. The backend catches errors, logs diagnostics, and returns structured responses. The API layer standardizes error formats and HTTP status codes. The frontend catches JavaScript errors with error boundaries, handles API errors with appropriate UI, retries transient failures automatically, and presents user-facing messages that are clear, actionable, and free of technical jargon.

The goal: users are never confused by an error. They know what happened, whether their data is safe, and what to do next. Developers are never blocked by an error report. They have the correlation ID, the stack trace, the request context, and the state information to diagnose and fix the issue quickly.

Next in the "Full-Stack Fundamentals" learning path: We'll cover deployment strategies for full-stack applications — how to ship backend and frontend changes safely and independently.

bottom of page