v0.1.xTypeScript · Zero deps · 7 error shapes

One function. Every error.

normalizeError turns Axios failures, failed fetches, GraphQL errors, Node transport errors, and thrown strings into a single predictable object — so your UI, retry logic, and logging code never need client-specific handling again.

Any error shape

Normalize Axios, Fetch, ky, Apollo, Node.js transport errors, thrown strings, and raw backend payloads.

Consistent output

Always returns { code, message, status, retryable, details } — the same shape regardless of what threw.

Zero config

Drop in normalizeError and handle every edge case without wrappers, adapters, or configuration.

Retry intelligence

The retryable flag lets you build retry logic in one place instead of inferring it from status codes per-client.

TypeScript-first

Fully typed output so your error handlers stay compile-safe across every client and scenario.

Preserve originals

The details field keeps the raw error accessible for Sentry, Datadog, or any other observability pipeline.

The Problem

Every HTTP client throws differently

Axios wraps errors in AxiosError with a response object. Fetch never throws on HTTP errors — you check response.ok yourself. ky throws HTTPError. GraphQL returns an errors array, not an exception. Node transport errors carry a code field like ECONNRESET instead of a status. Without a normalizer, every catch block needs client-specific logic that breaks when you switch libraries.

Without StatusForge
// Brittle: every client needs its own logic
try {
  await axios.get("/api/data");
} catch (err) {
  if (err.response?.status === 503) {
    setMsg(err.response.data.message ?? "Down");
    scheduleRetry();
  } else if (err.code === "ECONNRESET") {
    setMsg("Connection lost");
  } else if (typeof err === "string") {
    setMsg(err);
  } else {
    setMsg("Something went wrong");
  }
}
With StatusForge
// Clean: one function, every client
import { normalizeError } from "@npmforge/statusforge";

try {
  await axios.get("/api/data");
} catch (err) {
  const { message, retryable } = normalizeError(err);
  setMsg(message);
  if (retryable) scheduleRetry();
}
Output

Consistent output, every time

No matter what threw — or whether anything threw at all — you always get the same five fields back.

FieldTypeExampleDescription
codestring"SERVICE_UNAVAILABLE"Machine-readable token for switch/match logic and error tracking.
messagestring"Service temporarily unavailable"Human-readable explanation, safe to render directly in UI.
statusnumber | null503HTTP status code when available; null for transport or non-HTTP errors.
retryablebooleantrueWhether retrying the request is likely to succeed.
detailsunknown{ ... }Original error value preserved for logging and debugging.

Handles all of these out of the box

AxiosFetch APIkyApollo / GraphQLNode.js http/httpsThrown stringsRaw JSON payloads

Live Playground

HTTP client
Raw input (unknown)
Run a scenario to inspect its raw error value.
Normalized output
Run a scenario to inspect normalized output.
Ready100%

Get started in seconds

Install StatusForge and normalize every failure path the same way.

npm i @npmforge/statusforge