Skip to content
Start free
Back to Blog

5 node-fetch Alternatives Compared: Native Fetch, Axios, Got, Ky, and SuperAgent

Raluca PenciucLast updated on 13 min read
5 node-fetch Alternatives Compared: Native Fetch, Axios, Got, Ky, and SuperAgent
TL;DR: Start with native Fetch on supported Node.js versions, then add a dependency only when it replaces meaningful custom code. Axios favors cross-runtime convenience, Got offers deeper Node-specific control, Ky improves Fetch ergonomics, and SuperAgent suits fluent form and file workflows. Whatever you choose, preserve status, timeout, retry, cookie, and stream behavior during migration.

node-fetch is a lightweight Node.js package that provides a Fetch-compatible interface for making HTTP requests. Developers comparing node-fetch alternatives are usually deciding among three paths: keep the existing package, remove it in favor of the runtime's global Fetch API, or adopt a richer Node.js HTTP client.

That decision is less about finding a universal winner and more about matching request semantics to your workload. A small API client may only need fetch(), explicit status checks, and JSON parsing. A production integration may benefit from interceptors, phased timeouts, retry policies, proxy or agent control, cookie jars, upload helpers, or predictable streaming behavior.

This guide compares five credible options: native Fetch, Axios, Got, Ky, and SuperAgent. It separates built-in features from add-ons and application code, then shows where migrations commonly change behavior. You will also get an answer-first shortlist, a compatibility and capability matrix, paired migration code, and a scenario-based decision guide. Because package requirements and defaults change, time-sensitive version claims are flagged for verification rather than presented as permanent facts.

Quick answer: Best node-fetch alternative by use case

There is no universal winner among node-fetch alternatives. Start with the capability your application actually needs, then verify the installed major version and Node.js baseline.

Use case

Best first option

Why

Modern service with basic HTTP needs

Native Fetch

No client dependency, familiar Fetch semantics

Shared browser and server code

Axios

Request transforms, instances, interceptors

Node-only integration with granular controls

Got

Phased timeouts, hooks, streams, retry controls

Fetch-style code with less boilerplate

Ky

Fetch-compatible inputs plus convenience options

Forms, multipart uploads, and chainable calls

SuperAgent

Fluent API and file-oriented helpers

These choices fall into three categories: a runtime API, a Fetch wrapper, and full HTTP clients. Compare within the right category rather than treating every feature gap as a defect.

Should you replace node-fetch at all?

Treat the decision as keep, remove, or replace. Keep node-fetch when its current behavior is covered by tests, your runtime constraints justify it, and migration produces no concrete operational benefit. Remove it when your supported Node.js versions already expose global Fetch and your code only needs standards-style requests. Replace it when retries, hooks, phased timeouts, centralized transforms, or upload helpers are becoming application-maintained infrastructure.

A practical guide to making HTTP requests with node-fetch can still be useful when you are maintaining an existing integration. The package is not automatically the wrong choice simply because newer runtimes include Fetch. The relevant question is whether another option reduces risk or code without silently changing semantics.

Native Fetch versus another dependency

Native Fetch reduces dependency surface and aligns server code with the standardized Request, Response, Headers, and Body model. The official Node.js global Fetch documentation records its version and stability history, which you should check against the exact runtime deployed in production.

Do not rely on an assumed platform timeout. Set an explicit deadline with AbortController, or use AbortSignal.timeout() only after confirming that API exists in every supported Node.js version.

ESM, CommonJS, and supported Node.js versions

The captured node-fetch documentation describes v3 as ESM-only and v2 as CommonJS-compatible. It also reports bundled TypeScript declarations for v3 and an older minimum Node.js floor, but those details and maintenance guidance are time-sensitive. Confirm the package metadata before standardizing on either line.

Do the same for Axios, Got, Ky, and SuperAgent. Inspect engines, type, exports, and types with npm view <package> engines type exports types, then test the actual import form in CI. Current majors may differ in ESM support, CommonJS interoperability, and runtime requirements.

Compare the five node-fetch alternatives at a glance

Use this as a shortlist. B means built in, A add-on or adapter, and M manual application code. An asterisk marks a time-sensitive capability that needs release-specific verification.

Client

Best fit

Runtime/module

JSON / status

Deadline / cancel

Retry

Hooks

Proxy/agent

Cookies

Streams/uploads

HTTP/2

node-fetch

Existing Fetch code

Node; v2 CJS, v3 ESM*

M / M

M / B signal

M

M

B option*

M-A

B Node streams, FormData

M

Native Fetch

Minimal dependency

Supported Node*

M / M

M / B signal*

M

M

M or runtime hook*

M-A

B web streams, FormData

M or runtime*

Axios

Cross-runtime convenience

Node/browser; exports*

B / B error

B / B signal

A

B interceptors

B-A*

M-A

B-A*

A*

Got

Node service control

Node; current majors ESM*

B / B error

B / B*

B*

B

B agent*

A*

B streams

B*

Ky

Fetch ergonomics

Fetch runtimes; ESM*

B / B error

B* / B signal

B*

B

M via Fetch

M-A

B Fetch streams/FormData

M or runtime*

SuperAgent

Forms and files

Node/browser; exports*

B / B error*

B / abort API*

B*

A plugins*

B-A*

B-A agent*

B

A*

What matters before you switch clients

The longest feature list is rarely the best criterion. Define the request contract: body encoding, status errors, deadlines, retries, and the stream type passed downstream. A choice among node-fetch alternatives is safe only when those behaviors remain intentional.

This guide excludes speed rankings, download counts, stars, bundle-size claims, and maintenance leaderboards. Without fresh measurements, dates, and a stated method, those numbers create false precision rather than a sound engineering decision.

JSON, status errors, timeouts, and retries

Fetch-style clients usually require explicit JSON.stringify(), content headers, response parsing, and response.ok checks. Axios transforms object payloads, exposes parsed content on response.data, and rejects non-2xx responses by default unless validateStatus changes that rule. That difference can move code from a normal branch into catch.

Set deadlines explicitly regardless of client. Distinguish a total request deadline from connection, TLS, first-byte, and socket phases. Retries also need a written policy. Retrying GET after a transient failure is different from replaying a POST that may already have committed. Treat automatic retry support as a policy engine, not a checkbox.

Proxies, cookies, streams, uploads, and HTTP/2

For Node.js services and scraping, transport details often decide the client. Proxy support may come from a client option, an HTTP agent, a Fetch dispatcher, or an adapter. Cookie persistence usually requires a jar or application logic because server-side clients do not inherit a browser cookie store.

Check whether response bodies are Node.js Readable streams or WHATWG ReadableStream objects before changing download pipelines. Verify multipart FormData behavior, file size limits, redirect handling, decompression, and backpressure. HTTP/2 support may belong to the client, an extension, or the underlying runtime.

A practical guide to proxy configuration in node-fetch and a broader guide to web scraping with JavaScript and Node.js are natural companions when these transport concerns dominate the migration.

Native Fetch: the zero-dependency baseline

For supported runtimes, native Fetch should be the first baseline against which other node-fetch alternatives are judged. It preserves the familiar promise-based interface without an external client package, but it also preserves Fetch's deliberate explicitness: you serialize JSON, parse the response body, and decide what an HTTP error means.

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5_000);

try {
  const response = await fetch('https://api.example.com/items', {
    signal: controller.signal
  });

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  const items = await response.json();
  console.log(items);
} finally {
  clearTimeout(timer);
}

This example sets its own deadline rather than assuming an undocumented default. Network failure and cancellation reject the promise, while a 404 or 500 still produces a Response that your code must inspect. That behavior follows the broader Fetch Standard.

The main migration trap is streams. node-fetch exposes Node.js readable bodies, while native Fetch follows web-stream semantics. If existing code pipes directly into stream.pipeline, adapt or convert the body and regression-test backpressure, cancellation, and error propagation.

Axios: convenience across Node.js and browsers

Axios is a strong node fetch alternative when convenience should be centralized across browser and server code. Passing a plain object as request data triggers JSON serialization and appropriate content handling, and parsed response data is available through response.data. By default, non-2xx statuses become errors with server details on error.response.

Reusable instances let you standardize base URLs, headers, deadlines, and validateStatus. Interceptors are built-in middleware for authentication, tracing, logging, refresh flows, and response transformation. That can replace repeated wrapper code, but it can also hide behavior, so keep interceptor order and failure paths tested.

Retries are not part of Axios core in the captured evidence. Add them through an extension or application policy, and verify eligible methods. Proxy behavior can depend on protocol, environment variables, adapters, or custom agents, so test the exact deployment path rather than assuming one proxy option covers everything.

A dedicated Axios proxy setup guide and an Axios headers playbook are useful internal follow-ups. Also confirm the current package's Node.js floor, module exports, cancellation API, stream behavior, and any HTTP/2 adapter before committing.

Got: granular control for Node.js services

Got is the most Node.js-focused option in this set of node-fetch alternatives. Its value is control: separate timeout phases can cover DNS lookup, connection, TLS negotiation, request sending, first response byte, socket inactivity, or the entire lifecycle. That makes failure telemetry more actionable than a single generic timeout.

Got also exposes hooks and streaming APIs suited to service-to-service integrations. The captured documentation describes automatic retries with backoff, respect for Retry-After, and native HTTP/2 support, but defaults, eligible methods, and current transport behavior must be verified for the installed major. Never let a default retry policy replay a state-changing request without an idempotency strategy.

For scraping or outbound API gateways, inspect agent configuration, proxy routing, cookie-jar integration, decompression, and stream limits. Got's richer option surface can remove custom infrastructure, but it increases configuration responsibility. Prefer a shared, reviewed instance over per-call option sprawl.

Current releases are commonly documented as ESM-oriented and Node-only. Confirm engines, exports, bundled types, and maintenance status before choosing Got for a CommonJS service or an older runtime.

Ky: Fetch ergonomics with less boilerplate

Ky sits between native Fetch and a full Node.js HTTP client. It accepts Fetch-style inputs while adding method shortcuts, reusable instances, hooks, and request options that reduce repetitive code. That makes it attractive when you like Fetch semantics but want a smaller application wrapper.

Current documentation should be checked for the exact timeout, retry defaults, eligible methods, and error behavior. The captured evidence describes non-2xx responses as errors and retries as built in, both of which can change behavior when replacing node-fetch. Preserve your existing status branches deliberately rather than letting a convenience default decide.

Ky delegates important transport behavior to the underlying Fetch implementation. Proxy or dispatcher configuration, cookies, web streams, FormData, and HTTP/2 therefore depend partly on the runtime. Download progress has been documented in some releases, while upload progress support is more constrained, so verify both before designing telemetry around them.

Choose Ky among node-fetch alternatives when Fetch compatibility matters more than Node-specific transport control.

SuperAgent: chainable requests and file workflows

SuperAgent is a pragmatic node fetch alternative for teams that prefer a fluent API such as .get(), .set(), .send(), .field(), and .attach(). Its form and file helpers make multipart workflows readable, while built-in response parsing and progress-oriented APIs can simplify upload or download code.

The trade-off is semantic distance from Fetch. Error handling, timeout configuration, redirects, cancellation, and body access need a fresh test plan rather than an import swap. The source material contains conflicting claims about hooks and AbortController support. Treat plugins as add-ons, and confirm whether your selected release uses its own abort method, accepts signals, or requires a wrapper.

Likewise, verify retry behavior and eligible methods before enabling it, and do not assume HTTP/2 is core without current documentation. In Node.js, inspect agent, proxy, cookie persistence, and stream behavior for your exact workflow.

SuperAgent fits best when its chainable forms and file APIs replace meaningful custom code, not merely because the syntax looks concise.

Migrate from node-fetch without changing behavior

A safe migration starts by writing down existing semantics, then changing one layer at a time. If you move to native Fetch, the smallest behavior-preserving change may be removing the import while retaining explicit JSON serialization, status checks, and cancellation.

// Before
import fetch from 'node-fetch';
await sendJson(fetch);

// After
await sendJson(globalThis.fetch);

async function sendJson(fetchImpl) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), 5_000);

  try {
    const response = await fetchImpl('https://api.example.com/jobs', {
      method: 'POST',
      headers: {'content-type': 'application/json'},
      body: JSON.stringify({status: 'queued'}),
      signal: controller.signal
    });

    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return await response.json();
  } finally {
    clearTimeout(timer);
  }
}

When moving to Axios or another status-throwing client, configure its status policy or rewrite the surrounding control flow intentionally. Do not let a 404 move from a handled result into a generic retry path by accident.

Migration checklist: imports, bodies, errors, cancellation, and streams

  • Confirm ESM or CommonJS imports and the minimum deployed Node.js version.
  • Compare JSON, URL-encoded, Blob, File, and FormData serialization.
  • Preserve non-2xx handling, redirect modes and limits, and absolute-URL assumptions.
  • Verify cancellation error types and whether deadlines cover the whole lifecycle.
  • Remember that a consumed body cannot be read twice without cloning or buffering.
  • Convert Node streams and web streams explicitly, then test backpressure and partial failures.
  • Reconfigure proxies, agents or dispatchers, cookie jars, decompression, and response-size limits.
  • Add regression tests for uploads, large downloads, retries, duplicate POST protection, and cleanup after aborts.

Choose by project scenario

Use these scenario defaults to narrow the five node-fetch alternatives:

  • Minimal-dependency modern service: start with native Fetch.
  • Legacy CommonJS application: keep node-fetch v2 temporarily or choose a client whose current CommonJS path and Node floor are verified.
  • Shared browser and server code: evaluate Axios first, then Ky if Fetch compatibility is more important than interceptors.
  • Retry-heavy Node integration: evaluate Got, with an explicit idempotency policy.
  • Forms, attachments, and upload progress: evaluate SuperAgent or Axios against the exact workflow.
  • Proxy-based scraping: choose based on agent or dispatcher control, cookies, stream handling, and block-management needs, not request syntax alone.

Final recommendation

Evaluate native Fetch first on every Node.js version you actually support. It is the clearest baseline for deciding whether another dependency earns its place.

Choose Axios for cross-runtime conveniences, Got for deep Node.js controls, Ky for Fetch-style ergonomics, or SuperAgent for chainable form and file workflows. The best node fetch alternative is the one whose verified built-in features replace meaningful custom code while preserving your request contract.

Key Takeaways

  • Evaluate native Fetch first when every deployed Node.js version supports it and you only need explicit, standards-style HTTP behavior.
  • Choose a dependency for verified conveniences that remove custom code, not for the longest feature checklist.
  • Preserve JSON encoding, non-2xx handling, deadlines, cancellation, redirect, and retry semantics with regression tests.
  • Treat proxies, cookie jars, stream types, uploads, and HTTP/2 as transport concerns that may require agents, adapters, or plugins.
  • Check current package metadata and official documentation before relying on module formats, Node.js floors, or defaults.

FAQ

Can I keep node-fetch v2 in a CommonJS project instead of migrating?

Yes, if it remains compatible with your supported runtime, security policy, and maintenance expectations. Pin the version, review current project guidance, and keep request behavior covered by tests. Treat it as an explicit compatibility decision, not a permanent default. Plan an exit if a future Node.js upgrade, dependency policy, or unsupported transitive package makes continued use costly.

Most server-side clients need explicit cookie handling or a cookie-jar integration. Native Fetch and node-fetch do not behave like a browser cookie store. Other clients may integrate with jars, agents, or plugins, and a persistent SuperAgent agent may help in some versions. Verify domain, path, expiration, redirect, and concurrent-request behavior before relying on session persistence.

Should automatic retries apply to POST and other non-idempotent requests?

No, not by default. A timed-out POST may have reached the server even when the client never received a response. Retry it only when the operation is designed for replay, usually with an idempotency key, a server-side deduplication rule, or a safe application-specific contract. Also cap attempts and honor server backoff signals where appropriate.

What changes when streaming a large response with native Fetch instead of node-fetch?

The body typically changes from a Node.js Readable to a WHATWG ReadableStream. Existing .pipe() or stream.pipeline() code may therefore need conversion, such as Readable.fromWeb() where supported, or a web-stream pipeline. Test backpressure, abort propagation, partial files, decompression, and cleanup because successful small-buffer tests may not expose production streaming failures.

Conclusion

The right choice among node-fetch alternatives depends on which behavior you want to preserve and which infrastructure you no longer want to maintain. Native Fetch is the sensible baseline for supported runtimes because it removes a dependency while keeping familiar Fetch semantics. Axios adds cross-runtime transforms and interceptors, Got emphasizes detailed Node.js controls, Ky wraps Fetch with conveniences, and SuperAgent makes form and file workflows readable.

Before switching, inventory the contract around each request. Check imports, JSON serialization, non-2xx handling, deadlines, cancellation, retry eligibility, redirects, cookie persistence, proxy routing, uploads, and stream types. Then verify current package requirements and defaults against the exact major version you intend to install.

For scraping workloads, the HTTP client may be only one layer of the problem. If blocks, CAPTCHAs, and proxy rotation are consuming more effort than response handling, consider the Scraper API from WebScrapingAPI. It returns raw HTML while handling that request layer. Keep parsing and business logic in your application, and use the client choice that makes those remaining responsibilities clearest.

About the Author

Raluca Penciuc, Full-Stack Developer @ WebScrapingAPI

Raluca Penciuc

Full-Stack Developer

Raluca Penciuc is a Full Stack Developer at WebScrapingAPI, building scrapers, improving evasions, and finding reliable ways to reduce detection across target websites.

Start Building

Ready to Scale Your Data Collection?

Join 2,000+ companies using WebScrapingAPI to extract web data at enterprise scale with zero infrastructure overhead.