CodingNic

API, Persistence, and Hardening

Give API Failures a Consistent Shape

API, Persistence, and Hardening 18 min read

Give API Failures a Consistent Shape

An Express route can fail in two very different ways. A validation failure is expected and should be readable. An unexpected file or programming failure should not expose a stack trace to the browser.

Open server/src/asyncHandler.js and use the wrapper that turns a rejected route promise into an Express error:

javascript
export function asyncHandler(handler) {
  return function wrappedHandler(req, res, next) {
    Promise.resolve(handler(req, res, next)).catch(next);
  };
}

The route can now stay focused on its normal success path. If an async operation throws, Express receives the error through next instead of leaving an unhandled rejected promise.

Open server/src/app.js and look at the final error middleware. It preserves deliberate status codes such as 400, 404, and 409, but unexpected errors become a generic 500 response:

javascript
const statusCode = Number.isInteger(err.statusCode) ? err.statusCode : 500;
const message =
  statusCode >= 500
    ? "Could not save your changes. Please try again."
    : err.message;

Now open client/src/lib/api.js. The request helper handles the other half of the contract:

javascript
if (!response.ok) {
  const body = await response.json().catch(() => ({}));
  throw new Error(body.error || `Request failed (${response.status})`);
}

The browser therefore gets one normal JavaScript Error whether the server returned a validation message or the network request failed before a response existed.

There is one special success case to handle: HTTP 204 means “no content.” The helper returns null instead of attempting response.json() on an empty response.

Test it

Trigger a normal validation error and confirm the UI displays the server’s message. Then stop the API and try another action. The UI should report that it cannot reach the server rather than failing silently.

Checkpoint

You can now trace an API failure from its origin in an Express route all the way to the message the React UI displays.