universal-api-errors

Getting Started

Install universal-api-errors and normalize your first error — with a decision guide so you don't have to guess.

universal-api-errors turns whatever shape an error arrives in — Axios, fetch, or anything else — into one predictable, fully-typed UniversalError. No more if (error.response?.status === 401) scattered across every project, copy-pasted into every new one, slightly wrong every time.

There are five published packages. That's not a typo, and it's not five things you need to install — it's one core, two adapters for the two most common HTTP clients, one integration for the most common data-fetching library on top of them, and one "just give me the common stuff" bundle. Nobody installs all five. Most people install exactly one.

If you'd rather answer two quick questions than read the next section, here you go:

Two questions, one install command. No PhD in package archaeology required.

What sends your API requests?

Meet the family

Here's what's actually in each one — no marketing fog, just what's inside and when you'd reach for it.

@harshilrajput/universal-api-errors-core

The foundation. Everyone else is built on this. Zero runtime dependencies — not even Axios's types — because it recognizes error shapes by duck-typing instead of importing the libraries that produce them. Contains parseError(), the UniversalError model, status classification, validation normalization, retry helpers, and the logger. Install it directly if your HTTP client isn't Axios or fetch — it still works, via the same duck-typing every adapter is built on.

npm install @harshilrajput/universal-api-errors-core

@harshilrajput/universal-api-errors-axios

For when Axios is already sitting in your package.json and isn't going anywhere. Adds parseAxiosError(), which does the one thing the generic parser can't: reliably tells "the server responded with an error" apart from "the request never got a response at all." Re-exports all of core, so this one install is genuinely all you need.

npm install @harshilrajput/universal-api-errors-axios

@harshilrajput/universal-api-errors-fetch

Native fetch, zero extra dependencies, mildly smug about it. Adds parseFetchError() — the one async parser in the whole ecosystem, because reading a Response body is inherently asynchronous and there's no way around awaiting it once. Also re-exports all of core.

npm install @harshilrajput/universal-api-errors-fetch

@harshilrajput/universal-api-errors-react-query

Not a parser — React Query doesn't invent its own error format, so there's nothing new to parse. What it actually adds: useApiError(), a hook that pulls a normalized error straight out of a query/mutation result, and createRetry(), which makes React Query's retry option respect real error classification instead of retrying a 404 the same number of times as a network blip. Needs react as a peer dependency — this is the one package here that actually cares whether you're in a browser.

npm install @harshilrajput/universal-api-errors-react-query

@harshilrajput/universal-api-errors

The "I don't want to think about it" button. Bundles core + the Axios adapter + the fetch adapter into a single install. Deliberately does not include React Query — bundling a React-only hook into a package meant for anyone, including plain Node backends, would force react into dependency trees that have nothing to do with React. If you use React Query, add it separately; everything else, this one install covers.

npm install @harshilrajput/universal-api-errors
core
zero-dependency foundation
axios
adapter
fetch
adapter
react-query
integration
universal-api-errors
axios + fetch + core, bundled

The umbrella package combines axios + fetch — not react-query. See why above.

Quick start

import { parseAxiosError } from "@harshilrajput/universal-api-errors-axios";

try {
  await axios.get("/api/user");
} catch (err) {
  const error = parseAxiosError(err);

  error.message; // "Token Expired"
  error.status; // 401
  error.type; // "Unauthorized"
  error.retryable; // false
  error.isUnauthorized; // true
}

Every adapter re-exports the entire core package, so parseError, UniversalError, retry, and every helper are all available from the same import — no need to install or import core separately.

Why this exists

Every project ends up with the same repetitive error-parsing code, because every backend spells its errors differently, and every one of those spellings looked perfectly reasonable to whoever wrote that particular backend:

Laravel
{ "message": "Invalid Password" }
NestJS
{ "statusCode": 401, "message": "Token Expired", "error": "Unauthorized" }
FastAPI
{ "detail": [{ "loc": ["body", "email"], "msg": "field required" }] }

parseError() (and the adapters built on top of it) normalize all of these into the same UniversalError shape, so your error-handling code stops caring where the error came from — and stops getting rewritten from scratch on every new project.

Next steps

On this page