Back to Blogs
Web Development

Practical API Error Handling Patterns for Web and Mobile Clients

Learn concrete error‑handling patterns that keep web and mobile clients reliable, improve user experience, and simplify debugging for business‑critical applications.

Why Consistent Error Handling Matters

When a client application—whether a JavaScript‑based web portal or a native mobile app—receives an unexpected response, the user experience can degrade instantly. Inconsistent handling leads to vague error messages, duplicated code, and costly support tickets. For decision‑makers, this translates into higher maintenance overhead and slower time‑to‑value.

From a technical standpoint, a well‑defined error contract lets front‑end teams write deterministic code, reduces the need for ad‑hoc try/catch blocks, and makes monitoring easier. The patterns described below assume a JSON‑based API, which is the most common format for modern business applications built with JavaScript, Django, or .NET backends.

Consistent error handling also enables centralized logging and alerting, so operations teams can spot spikes in specific error codes before they impact customers.

Standardize the Error Response Schema

All endpoints should return a predictable JSON object when something goes wrong. A minimal schema includes:

  • code: an application‑specific error identifier (e.g., USER_NOT_FOUND)
  • message: a short, human‑readable description
  • details (optional): additional context such as validation field errors
  • status: the HTTP status code (e.g., 404, 422)

Example:

{"code":"USER_NOT_FOUND","message":"The requested user does not exist.","status":404}

By enforcing this schema at the API gateway or controller layer, both web and mobile teams can rely on a single parsing routine, reducing duplicated logic across platforms.

When APIs evolve, version the error schema alongside the payload so older clients continue to interpret errors correctly.

Map HTTP Status Codes to Business Scenarios

HTTP status codes already convey a lot of meaning, but they are often over‑used or mis‑used. Align each status with a clear business scenario:

  • 400 Bad Request: client sent malformed data; use for validation failures.
  • 401 Unauthorized: authentication token missing or expired.
  • 403 Forbidden: authenticated user lacks required permissions.
  • 404 Not Found: resource does not exist; useful for soft‑delete checks.
  • 409 Conflict: business rule violation, such as duplicate order numbers.
  • 422 Unprocessable Entity: semantic errors that pass schema validation but fail business logic.

When the client receives a status code that matches the business intent, it can decide whether to retry, prompt the user, or silently ignore the response. This reduces unnecessary network traffic and improves perceived performance.

Documenting these mappings in a shared reference sheet helps keep front‑end and back‑end teams aligned during sprint planning.

Implement Centralized Error Interceptors

Both JavaScript (e.g., using Axios or Fetch) and mobile SDKs (e.g., Retrofit for Android, Alamofire for iOS) support request/response interceptors. Place a single interceptor that:

  • Detects non‑2xx responses.
  • Parses the standardized error schema.
  • Logs the error with correlation IDs for backend tracing.
  • Transforms the error into a domain‑specific exception or error object.

Example for a JavaScript client using Axios:

axios.interceptors.response.use(
  response => response,
  error => {
    const errData = error.response?.data || {};
    const apiError = {
      code: errData.code || 'UNKNOWN_ERROR',
      message: errData.message || error.message,
      status: error.response?.status || 500,
    };
    // Attach correlation ID for later debugging
    apiError.correlationId = error.response?.headers['x-correlation-id'];
    return Promise.reject(apiError);
  }
);

Mobile platforms follow the same principle: a single network layer translates raw HTTP failures into typed exceptions that UI components can consume.

Unit tests for the interceptor should cover each error shape to guarantee consistent handling across releases.

Provide User‑Friendly Feedback While Preserving Technical Detail

End users should see concise, actionable messages. Internally, you still need the full error payload for debugging. A common pattern is to separate “displayMessage” from “technicalMessage” in the response:

{"code":"PAYMENT_DECLINED","message":"Your payment could not be processed.","displayMessage":"We couldn’t complete the purchase. Please check your card details.","status":402}

Front‑end code shows displayMessage to the user, while logging message and code for support teams. This approach prevents leaking internal details (e.g., database errors) to customers and keeps compliance requirements satisfied.

When localization is required, keep the displayMessage keys separate so translation pipelines can replace them without touching the technical fields.

Leverage Retries and Idempotency for Transient Failures

Network glitches, rate limits, or temporary service outages are inevitable. For idempotent operations—such as GET requests or POSTs that include a client‑generated idempotency key—implement automatic retries with exponential backoff. Ensure the server respects the idempotency key so repeated attempts do not create duplicate records.

In practice, wrap the retry logic in the same interceptor that handles error parsing. Limit retries to three attempts and surface a clear message if the operation still fails, so users are not left waiting indefinitely.

Additionally, provide a graceful fallback UI that informs users of the temporary issue and offers a manual retry button.

Related reading: Practical Deployment Basics for Business Web Applications.