mrkeyoor.com_
Wed 23 Sept 00:35 UTC
npmUtilsupdated 22 Sept 2026

node-fetch-h2 review

node-fetch-h2 2.3.1-0 is a 2018 fork of node-fetch 2.3.0 that sends requests through `http2-client` instead of Node's `http.request()` and `https.request()`. The rest of the public surface is the old node-fetch API: a CommonJS `fetch` function plus Request, Response, Headers, FetchError, Node streams, redirects, decompression, abort signals, timeouts, and response-size limits. The prerelease's only package change from 2.3.0 is an `http2-client` range bump from `^1.2.5` to `^1.3.0`; its copied README still tells users to install `node-fetch` and never explains HTTP/2 behavior.

Verdict

node-fetch-h2 2.3.1-0 installed in 1.1 seconds and used 1 MB across 2 packages in our sandbox, yet its redirect code can forward credentials to another host and discard size and timeout limits. Do not install it for new work; pin it only while removing a tested legacy dependency.

We installed it

Lab card: what happened when we installed node-fetch-h2Screenshot of node-fetch-h2 documentation
Install✓ · 1.1s2 packages on disk · 1 MB
ImportESM import works · require() works · CommonJS package
Browser0.5 KBgzipped (0.8 KB minified), bundled with esbuild
Typesno TypeScript types found
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does node-fetch-h2 install cleanly?

Yes. In a fresh container with an empty cache, npm install node-fetch-h2 finished in 1 seconds, leaving 2 packages and 1 MB on disk. npm audit reported no known vulnerabilities.

How much does node-fetch-h2 add to a browser bundle?

0.5 KB gzipped (0.8 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does node-fetch-h2 work with both ESM and CommonJS?

Yes. Both import 'node-fetch-h2' and require('node-fetch-h2') worked in Node 22 in our run. The package is published as CommonJS.

Does node-fetch-h2 include TypeScript types?

No type declarations were found in our install, so TypeScript users need their own declarations.

node-fetch-h2 or undici: which should you use?

undici: Use it for a maintained Node HTTP client with fetch support and explicit opt-in HTTP/2 clients. node-fetch-h2 2.3.1-0 installed in 1.1 seconds and used 1 MB across 2 packages in our sandbox, yet its redirect code can forward credentials to another host and discard size and timeout limits.

When should you not use node-fetch-h2?

You are choosing a client today. The repository's last push and npm publish were both in November 2018, and the package has 1 GitHub star.

API stability3/5node-fetch-h2 exposes the node-fetch 2.3.0 shape and has not changed since 2018, so fetch, Headers, Request, Response, FetchError, and Node body streams are unlikely to surprise an existing caller. The latest tag is `2.3.1-0`, a prerelease whose only tarball change is the transport dependency range. Stability here comes from abandonment, not a supported compatibility promise. The stale surface also preserves redirect behavior that later client implementations corrected.
Docs1/5The repository README is a copied node-fetch manual. It tells readers to install and require `node-fetch`, links to files in the upstream `bitinn/node-fetch` repository, and documents the old fetch options at length. It gives 0 explanation of `http2-client`, ALPN negotiation, HTTP/1.1 fallback, session reuse, or protocol visibility. The package's defining transport swap is discoverable only from the one-line npm description and the `h2.request` call in source.
Maintenance1/5npm published 2.3.0 on November 19, 2018 and 2.3.1-0 on November 30, 2018. GitHub reports the same November 30 date for the last push, 1 star, and 0 open issues or pull requests. The repository is not archived, but 8 years without a commit or release is stronger evidence than that flag. The code remains tied to an old node-fetch snapshot and an engine declaration beginning at Node 4.
Ecosystem2/5The npm endpoint counted 4,656,988 downloads from August 18 through 24, 2026, despite a repository with 1 star and no activity since 2018. That mismatch is a reason to inspect dependency trees rather than infer an active user community. The package has 1 runtime dependency, no peer dependencies, no types, no provider adapters, and no HTTP/2 control API. Knowledge transfers to node-fetch 2, while maintained clients such as undici and got have their own current ecosystems.

Use it if

  • A lockfile already contains node-fetch-h2 and you need to understand the HTTP/2 transport before replacing it.
  • Legacy code depends on node-fetch 2 classes and Node Readable response bodies while an upstream endpoint negotiates HTTP/2.
  • Your test matrix includes the exact 2.3.1-0 prerelease and you can isolate its redirect behavior behind a trusted single host.
  • A transitive dependency cannot yet be removed, so pinning and compensating controls are more practical than an immediate client migration.
Skip it if

Setup reality

Our fresh Node 22 install of node-fetch-h2 2.3.1-0 completed in 1.1 seconds. It left 2 packages and 1 MB on disk; the package itself was 172 KB unpacked. npm audit reported 0 known vulnerabilities. The manifest has 1 direct dependency, no peers, an MIT license, and an engine range of Node 4.x || >=6.0.0. The package is CommonJS with no exports map or TypeScript declarations. Both require() and ESM import loaded it in our sandbox.

There are no credentials or configuration files, but the release channel is unusual. npm's latest tag points to the prerelease 2.3.1-0. The only tarball difference from 2.3.0 is the http2-client range changing from ^1.2.5 to ^1.3.0. Its README is copied from node-fetch: the install command says npm install node-fetch, examples require node-fetch, and HTTP/2 is not described. Pin the exact build if an inherited dependency must stay.

Automatic redirects are the reason to walk away from credentialed use. The source creates the next Request with a fresh copy of every header, even when Location points to another host. It also leaves size and timeout out of the new options, resetting both limits. Use redirect: 'manual' and rebuild an allowed next request, or migrate. HTTP 4xx and 5xx responses still resolve normally, so callers must check response.ok or status.

The transport delegates pooling, ALPN, and HTTP/1.1 fallback to http2-client, while the fetch result does not report which protocol won. Server response bodies are Node Readable streams rather than the WHATWG streams returned by current global fetch. Our browser bundle measured 0.8 KB minified and 0.5 KB gzipped because the browser entry simply returns global fetch; that figure says nothing about the server transport. Use AbortSignal for cancellation, close or drain bodies, and migrate new code to a maintained client.

Patterns

Read JSON and check the status fetch-json

const fetch = require('node-fetch-h2');

const response = await fetch('https://api.example.com/users');
if (!response.ok) {
  throw new Error(`HTTP ${response.status}`);
}
const users = await response.json();

A 4xx or 5xx response resolves normally; only transport, abort, and body-processing failures reject the promise.

Lock the exact published build pin-prerelease

{
  "dependencies": {
    "node-fetch-h2": "2.3.1-0"
  }
}

npm marks `2.3.1-0` as latest, while ordinary semver ranges can exclude prereleases; use an exact version for repeatable legacy installs.

Send a JSON request body post-json

const fetch = require('node-fetch-h2');

const response = await fetch('https://api.example.com/jobs', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({task: 'sync'}),
});

A string body does not infer JSON; set `Content-Type` yourself and check the returned status before reading the body.

Keep credentials on one host handle-redirect-manually

const fetch = require('node-fetch-h2');
const {URL} = require('node:url');

async function fetchOneHost(url, token) {
  const first = new URL(url);
  const response = await fetch(first, {
    redirect: 'manual',
    headers: {Authorization: `Bearer ${token}`},
  });
  if (response.status < 300 || response.status >= 400) return response;

  const next = new URL(response.headers.get('location'), first);
  if (next.origin !== first.origin) throw new Error('cross-host redirect');
  return fetch(next, {redirect: 'manual', headers: {Authorization: `Bearer ${token}`}});
}

Version 2.3.1-0 copies every header during automatic redirects without an origin comparison, so manual handling is required for secrets.

Reapply limits after each redirect preserve-response-limit

const LIMIT = 5 * 1024 * 1024;

const response = await fetch(url, {
  redirect: 'manual',
  size: LIMIT,
  timeout: 5_000,
});

const length = Number(response.headers.get('content-length') || 0);
if (length > LIMIT) throw new Error('response too large');

Automatic follow omits both `size` and `timeout` from its next Request; manual hops must pass the 5 MB and 5-second limits again.

Cancel a slow operation abort-request

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

try {
  const response = await fetch(url, {signal: controller.signal});
  return await response.json();
} finally {
  clearTimeout(timer);
}

AbortController is absent from the old Node 4 and 6 environments named by this package, so those runtimes require a compatible polyfill.

Pipe a response into a file stream-download

const fs = require('node:fs');
const {pipeline} = require('node:stream/promises');

const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
await pipeline(response.body, fs.createWriteStream('artifact.bin'));

`response.body` is a Node Readable stream; code written for the WHATWG stream returned by global fetch needs an adapter.

Separate HTTP and socket failures classify-failure

try {
  const response = await fetch(url);
  if (!response.ok) {
    return {kind: 'http', status: response.status};
  }
  return {kind: 'ok', value: await response.json()};
} catch (error) {
  return {kind: 'transport', code: error.code, type: error.type};
}

FetchError exposes transport details such as `type` and sometimes `code`; an HTTP 500 remains a Response.

Inspect repeated headers read-response-headers

const response = await fetch(url);

console.log(response.status);
console.log(response.headers.get('content-type'));
console.log(response.headers.raw());

`.raw()` is a node-fetch extension that returns header arrays; browser fetch and Node's global Headers do not share that exact API.

Override the package during migration replace-transitive-client

{
  "overrides": {
    "node-fetch-h2": "npm:node-fetch@^2.7.0"
  }
}

The node-fetch 2 API is close enough for many callers, but this override removes HTTP/2 negotiation and must be tested against the real upstream service.

Use Node's built-in fetch migrate-to-global-fetch

const response = await fetch(url, {
  signal: AbortSignal.timeout(5_000),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const payload = await response.json();

Current Node fetch returns WHATWG streams and has different extension methods, so audit `.buffer()`, `.raw()`, timeout, and agent usage during migration.

Create a maintained HTTP/2 client opt-into-undici-http2

const {Client} = require('undici');

const client = new Client('https://api.example.com', {allowH2: true});
try {
  const {statusCode, body} = await client.request({
    path: '/users',
    method: 'GET',
  });
  console.log(statusCode, await body.json());
} finally {
  await client.close();
}

Undici makes HTTP/2 an explicit per-client option and exposes a maintained connection lifecycle instead of hiding it behind a 2018 fork.

Alternatives

PackageRegistryPick it when
undicinpmUse it for a maintained Node HTTP client with fetch support and explicit opt-in HTTP/2 clients.
fetch-h2npmEvaluate it when a fetch-shaped HTTP/2 client is required and Node's built-in fetch is insufficient.
node-fetchnpmUse it for older Node applications that need the upstream fetch API and can stay on HTTP/1.1.
gotnpmUse it when retries, hooks, pagination, and richer request controls matter more than browser-fetch parity.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.