Trust is earned, not given

A different perspective

2022-04-19 · Projects

Node.js, part 16: Node 18 — global fetch, Web Streams, and the shrinking dependency list

Part 16from the Node.js series · 24 parts in all

Node 18 shipped in April 2022 with fetch as a global. Read casually that sounds like convenience; in practice it ended a decade of one of the ecosystem's strangest dependencies — every server carried an HTTP client it had to choose and justify. Part 16 is what arrived and where fetch is still the wrong tool.

fetch, global, no import

const res = await fetch('https://api.example.com/items', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ ids: [1, 2, 3] }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
const items = await res.json();

It is the WHATWG implementation, not a Node flavour, which is why it came with the rest of the web platform in one release: fetch, Headers, Request/Response, FormData, Blob, and ReadableStream. Sharing an API with the browser is the actual prize — HTTP code stops being runtime-specific, and that is worth more than the import line.

Web Streams: two families in one runtime

Now Node has both its own streams (part 3) and the web ReadableStream family. They interop through Readable.fromWeb() and Readable.toWeb(), and you should know which side you are on:

// The response body is a web stream; Node's pipeline wants a Node stream.
const { Readable } = require('stream');
const { pipeline } = require('stream/promises');
const fs = require('fs');

const res = await fetch(url);
await pipeline(Readable.fromWeb(res.body), fs.createWriteStream('download.bin'));

That pattern — fetch a large file and stream it straight to disk — is the one that matters, because await res.arrayBuffer() puts the whole thing in memory.

Where fetch is the wrong tool

The dependency list for a typical service shrank by three or four packages in this release. Next: the test runner and watch mode that removed a few more.