# npm fetch vs node fetch: Choosing the Right HTTP Client Among Node Fetch Alternatives

> Explore npm fetch vs node fetch to find the best HTTP client for your Nodejs project. Understand built-in features and choose the right tool for your needs.

- Repository: [Node.js/node](https://github.com/nodejs/node)
- Tags: comparison
- Published: 2026-02-19

---

**npm-registry-fetch is a specialized npm registry client with built-in authentication, retries, and caching, while node-fetch (Undici) provides a generic, standards-compliant HTTP implementation without npm-specific features.**

When evaluating node fetch alternatives for your Node.js applications, understanding the architectural differences between npm-registry-fetch and the built-in node-fetch implementation is crucial. Both reside in the Node.js source repository (`nodejs/node`), but they serve fundamentally different purposes—one as a registry-specific tool optimized for npm operations, and the other as a generic HTTP client following web standards.

## Core Architectural Differences

### npm-registry-fetch Registry-Specific Design

npm-registry-fetch, located at [`deps/npm/node_modules/npm-registry-fetch/lib/index.js`](https://github.com/nodejs/node/blob/main/deps/npm/node_modules/npm-registry-fetch/lib/index.js), functions as a specialized wrapper around make-fetch-happen. Its architecture centers on npm registry operations, automatically handling npm scopes, authentication tokens, and registry URLs. The library exposes a main `regFetch` function along with convenience methods like `json` and `json.stream` (implemented at lines 72-88 of the entry file) that provide parsed JSON responses or streaming parsers specifically designed for npm registry data.

The library's architecture consists of three main components: option merging with defaults defined in [`default-opts.js`](https://github.com/nodejs/node/blob/main/default-opts.js) (referenced at line 14 of [`lib/index.js`](https://github.com/nodejs/node/blob/main/lib/index.js)), header construction that injects npm auth information via `getHeaders` (lines 14-44), and request execution via make-fetch-happen, which itself uses minipass-fetch for the low-level HTTP handling while providing on-disk caching and retry semantics.

### node-fetch (Undici) Standards-Compliant Implementation

In contrast, node-fetch as implemented in [`deps/undici/src/lib/web/fetch/index.js`](https://github.com/nodejs/node/blob/main/deps/undici/src/lib/web/fetch/index.js) follows the WHATWG fetch specification almost verbatim. The core algorithm, starting around lines 34-45, handles request parsing, redirect logic, CORS policies, and abort controllers. Unlike npm-registry-fetch, this implementation knows nothing about npm registries or authentication schemes. It is exposed globally in [`deps/undici/src/index.js`](https://github.com/nodejs/node/blob/main/deps/undici/src/index.js) at line 220 via `globalThis.fetch = module.exports.fetch`, making it available as the built-in fetch in modern Node.js versions.

The implementation parses input into a `Request` object, determines the request's mode and redirect handling, delegates actual network I/O to Undici's HTTP client via `httpFetch` and `httpRedirectFetch`, and returns a `Response` implementing streaming via Node's `Readable` streams.

## Authentication and Registry Features

### Built-in npm Authentication

npm-registry-fetch automatically constructs authentication headers through its `getHeaders` function (lines 14-44 of [`lib/index.js`](https://github.com/nodejs/node/blob/main/lib/index.js)). It handles `npm-auth-type`, `npm-scope`, `npm-session`, and `npm-otp` headers, supporting both bearer tokens and basic authentication. The library can also trigger OTP prompts for two-factor authentication, as seen in the OTP handling block around lines 44-60.

### Manual Header Management in node-fetch

When using node-fetch (Undici), developers must manually construct any `Authorization` headers. The implementation provides no built-in support for npm tokens, OTP handling, or registry-specific authentication schemes. This generic approach offers flexibility for any HTTP endpoint but requires additional boilerplate when interacting with authenticated npm registries.

## Retry Logic and Caching Capabilities

### npm-Specific Retry and Disk Caching

npm-registry-fetch provides configurable retry mechanisms through options like `fetchRetries`, `fetchRetryFactor`, `fetchRetryMintimeout`, and `fetchRetryMaxtimeout`, constructed around lines 124-131 of the entry file. It leverages make-fetch-happen for persistent on-disk caching, with cache modes determined by `getCacheMode` (lines 707-711), supporting offline mode (`opts.offline`), prefer-offline, and prefer-online strategies.

### Standard HTTP Caching in node-fetch

node-fetch (Undici) follows standard HTTP cache-control semantics without persistent disk caching. It does not implement automatic retry logic—callers must handle retries manually or wrap the fetch call. This aligns with the WHATWG specification but means developers lose the resilience features built into npm-registry-fetch when using the generic implementation.

## Practical Code Examples

### Using npm-registry-fetch for Registry Operations

```javascript
const npmFetch = require('npm-registry-fetch');

// Fetch package manifest with npm auth and retry logic
const opts = {
  fetchRetries: 3,
  fetchRetryFactor: 2,
  fetchRetryMintimeout: 1000, // 1s
  fetchRetryMaxtimeout: 5000, // 5s
  cache: `${process.env.HOME}/.npm/_cacache`,
  otpPrompt: async () => {
    // Custom OTP handling for 2FA
    return await readUserInput('Enter OTP: ');
  }
};

npmFetch.json('lodash', opts)
  .then(pkg => {
    console.log('Latest version:', pkg['dist-tags'].latest);
  })
  .catch(err => {
    console.error('Registry fetch failed:', err);
  });

```

*Source references*: Option merging occurs in [`default-opts.js`](https://github.com/nodejs/node/blob/main/default-opts.js) (referenced at line 14 of [`lib/index.js`](https://github.com/nodejs/node/blob/main/lib/index.js)); header construction with npm auth happens in `getHeaders` (lines 14-44); retry configuration is built around lines 124-131; OTP handling appears at lines 44-60.

### Using node-fetch for Generic HTTP Requests

```javascript
// Node-fetch (Undici) is available globally in Node.js 18+
// or explicitly import from undici
const { fetch, Headers } = require('undici');

async function fetchRepositoryData(url) {
  try {
    const response = await fetch(url, {
      method: 'GET',
      headers: new Headers({ 
        'Accept': 'application/json',
        'Authorization': 'Bearer YOUR_TOKEN' // Manual auth required
      })
      // No automatic retry; implement manually if needed
    });

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

    // Streaming response body
    const data = await response.json();
    return data;
  } catch (error) {
    // Standard TypeError for network failures per WHATWG spec
    console.error('Fetch failed:', error);
    throw error;
  }
}

fetchRepositoryData('https://api.github.com/repos/nodejs/node')
  .then(data => console.log('Stars:', data.stargazers_count))
  .catch(console.error);

```

*Source references*: Global export occurs at line 220 of [`deps/undici/src/index.js`](https://github.com/nodejs/node/blob/main/deps/undici/src/index.js); core fetch algorithm implemented in [`deps/undici/src/lib/web/fetch/index.js`](https://github.com/nodejs/node/blob/main/deps/undici/src/lib/web/fetch/index.js) starting at lines 34-45; request/response handling uses standard WHATWG classes defined in [`deps/undici/src/lib/web/fetch/request.js`](https://github.com/nodejs/node/blob/main/deps/undici/src/lib/web/fetch/request.js) and [`response.js`](https://github.com/nodejs/node/blob/main/response.js).

### Side-by-Side Comparison

```javascript
const npmFetch = require('npm-registry-fetch');
const { fetch } = require('undici');

// Both can retrieve npm package data, but with different approaches
Promise.all([
  // npm-registry-fetch: handles auth, retries, and JSON parsing automatically
  npmFetch.json('express', { fetchRetries: 2 }),
  
  // node-fetch: raw HTTP, manual headers, no retries
  fetch('https://registry.npmjs.org/express')
    .then(r => r.json())
])
  .then(([npmData, fetchData]) => {
    console.log('npm-registry-fetch version:', npmData['dist-tags'].latest);
    console.log('node-fetch version:', fetchData['dist-tags'].latest);
  });

```

Both calls retrieve the same data, but the npm-registry-fetch version automatically adds npm auth headers, respects npm's proxy settings, and retries on transient failures, while the plain node-fetch version performs a raw HTTP GET.

## Summary

- **npm-registry-fetch** is purpose-built for npm registry interactions, offering automatic authentication, configurable retry logic, persistent disk caching, and npm-specific error handling.
- **node-fetch** (Undici) provides a standards-compliant WHATWG fetch implementation suitable for generic HTTP requests, requiring manual configuration for authentication and retries but offering maximum flexibility.
- Choose **npm-registry-fetch** when publishing packages, auditing, or interacting with private npm registries where npm authentication and resilience features are required.
- Choose **node-fetch** (the built-in global fetch) for general-purpose HTTP clients, REST API interactions, or when browser compatibility is essential.

## Frequently Asked Questions

### What is the main difference between npm-registry-fetch and node-fetch?

The primary distinction lies in their scope and built-in capabilities. **npm-registry-fetch** is a specialized client designed exclusively for npm registry operations, automatically handling npm authentication tokens, OTP prompts, registry-specific headers, and persistent caching. **node-fetch** (implemented via Undici) is a generic, standards-compliant HTTP client that implements the WHATWG fetch specification without any npm-specific logic, requiring developers to manually handle authentication and retries.

### Does node-fetch support automatic retries like npm-registry-fetch?

No, **node-fetch** (Undici) does not implement automatic retry logic. According to the WHATWG fetch specification implemented in [`deps/undici/src/lib/web/fetch/index.js`](https://github.com/nodejs/node/blob/main/deps/undici/src/lib/web/fetch/index.js), the fetch algorithm performs no automatic retries on transient failures. Developers must implement their own retry wrappers or use external libraries. In contrast, **npm-registry-fetch** provides configurable retry mechanisms through options like `fetchRetries`, `fetchRetryFactor`, and `fetchRetryMintimeout`, constructed around lines 124-131 of [`deps/npm/node_modules/npm-registry-fetch/lib/index.js`](https://github.com/nodejs/node/blob/main/deps/npm/node_modules/npm-registry-fetch/lib/index.js).

### Which library should I use for publishing packages to a private npm registry?

You should use **npm-registry-fetch** when publishing packages or interacting with private npm registries. This library, located at [`deps/npm/node_modules/npm-registry-fetch/lib/index.js`](https://github.com/nodejs/node/blob/main/deps/npm/node_modules/npm-registry-fetch/lib/index.js), automatically constructs npm authentication headers through its `getHeaders` function (lines 14-44), supports OTP prompts for two-factor authentication (lines 44-60), and handles registry-specific configuration such as scopes and proxy settings. While you could theoretically use **node-fetch** for registry requests, you would need to manually implement all npm authentication logic, token handling, and registry URL resolution.

### Is npm-registry-fetch available as a standalone package outside of Node.js?

Yes, **npm-registry-fetch** is available as a standalone package on npm and can be installed independently of the Node.js bundled version. While the source code analyzed here resides in the Node.js repository at `deps/npm/node_modules/npm-registry-fetch/`, the package is maintained separately by npm, Inc. and published to the npm registry. You can install it via `npm install npm-registry-fetch` to use its registry-specific features in applications that do not rely on the Node.js bundled npm client. In contrast, **node-fetch** (Undici) is bundled with Node.js 18+ as the global `fetch`, but can also be installed separately via `npm install undici` if you need specific versions or features.