Axios → fetch in Node.js: avoid silent changes to errors and timeouts

Node.jsHTTPdependency migration

Green tests do not prove that built-in fetch will behave like Axios in production. Here is how to check statuses, timeouts, proxies, TLS, and telemetry before switching without silent failures

Green tests can hide a behavior change

The pull request looks simple: remove the Axios package, change a few calls, and watch the tests pass. After deployment, however, an external service returns 500 and the application no longer enters its usual error-handling branch. The request technically completed, but its result was interpreted differently.

That is how a mechanical import replacement becomes a nighttime alert. The team has a reasonable goal: reduce dependencies and use an API built into Node.js. Users should not have to test the compatibility on the team’s behalf.

This change moves more than syntax. It moves a behavioral contract: which statuses count as errors, when a request stops, how its body is read, and which details reach logs and traces.

Find the work Axios is doing out of sight

Search for more than axios.get and axios.post. Inspect axios.create, global defaults, interceptors, custom wrappers, and modules with names such as apiClient or httpService.

For each request path, record the base URL, authentication headers, query parameters, body format, timeout, retries, and expected response shape. Mark uses of validateStatus, proxy, httpsAgent, responseType, and redirect handling. A shared interceptor may be applying rules that are invisible at the call site.

Move on only when the team can show the path from business code to the network and name every policy applied along that path.

Record compatibility before rewriting code

The official Node.js migration article highlights a crucial difference: Axios normally rejects its promise for statuses outside the successful range, while fetch returns a regular Response. A 404 or 500 therefore requires an explicit check of response.ok or response.status.

AreaWhat to verify
StatusesWhich codes become errors and which fields the error exposes
BodyWhen to read JSON, text, or a stream; how to handle empty or malformed JSON
TimeoutWhich AbortSignal stops the request and how timeout differs from manual cancellation
RedirectWhether redirects are allowed and what happens to sensitive headers
Proxy and TLSHow routing and certificates are configured on your actual Node.js versions
StreamingWhether the code can use fetch streams without buffering the entire body

Do not transfer an Axios option merely because another setting has a similar name. For example, httpsAgent is not a ready-made fetch configuration. Verify the documented mechanism for your Node.js version and real network route.

Build a thin adapter, not a copy of Axios

The fetch API has no Axios interceptor interface. Put repeated authentication, logging, tracing, and error-classification rules in a small application-owned adapter:

async function fetchJson(url, options = {}) {
  const response = await fetch(url, options);
  const text = await response.text();
  const data = text ? JSON.parse(text) : null;

  if (!response.ok) {
    const error = new Error(`HTTP ${response.status}`);
    error.status = response.status;
    error.body = data;
    throw error;
  }

  return { status: response.status, headers: response.headers, data };
}

const result = await fetchJson(url, {
  signal: AbortSignal.timeout(5_000)
});

This is only a starting shape. The team must define policies for empty bodies, malformed JSON, and timeouts. Do not attempt to reproduce every Axios feature. Also avoid automatically retrying every request: repeating a POST may duplicate an action on the server.

Test both clients against the same contract

Create a shared send(request) interface and run identical contract tests against Axios and the new adapter. A local HTTP server or controlled mock service should return 200, 404, 500, a delayed response, an empty body, malformed JSON, a redirect, and an early connection close.

Test more than the final result. Confirm authentication and tracing headers, the error shape, the cancellation reason, and the absence of unsafe retries. Test proxies, TLS, and large streams separately in an environment with the real network configuration.

Roll out the change so it can be stopped

Start with one low-risk external route or a small share of traffic. Compare rates of 4xx/5xx responses, timeouts, cancellations, and body parsing errors, as well as latency at high percentiles. Logs from the old and new paths should carry the same request identifiers.

Keep a configuration switch for a fast rollback. Do not send shadow copies of requests with side effects merely to compare clients. Agree on stop conditions—such as a rise in timeouts or missing traces—before deployment, not after the first incident.

Choose according to risk, not fashion

Migrate now when Axios use is centralized, the contract is documented, and network scenarios have been tested. Isolate Axios behind an adapter first when calls and interceptors are scattered across the codebase. Keep it when complex proxy, TLS, or streaming behavior is already reliable and the benefit of replacement does not cover the verification cost.

A removed package is not the result that matters. The real result is predictable HTTP behavior and a rollback the team can actually perform.

Sources

Quick checklist

  • find every direct and indirect Axios call
  • record required error and timeout behavior
  • verify proxy, TLS, redirect, and streaming behavior on the target Node.js version
  • run the same contract tests against both clients
  • add metrics, a switch, and a tested rollback path

Safe Axios-to-fetch migration plan

Help me plan a migration of an HTTP client from Axios to the built-in fetch API in Node.js. Inputs: - Node.js versions used in development, CI, and production; - examples of Axios calls and shared wrappers; - axios.create, defaults, and interceptor configuration; - rules for 4xx/5xx responses, timeouts, cancellation, and retries; - proxy, TLS, redirect, streaming, authentication, and tracing requirements; - available tests, metrics, staged rollout method, and rollback mechanism. Do not assume that the replacement is automatically compatible. Mark missing information and questions that require verification. Return the result in this exact format: 1. Short risk summary. 2. Axios usage map. 3. Difference table: current behavior, required behavior, test, and risk. 4. Minimal fetch adapter interface. 5. Contract test suite with concrete scenarios. 6. Staged rollout plan, metrics, and stop conditions. 7. Rollback plan. 8. Recommendation: migrate now, isolate Axios first, or keep it.