Skip to the solution
MediumBackend7 min read

What is undici and why is the global fetch built on it in modern Node.js?

376 views

The problem

Question presented to candidate: "You call fetch() in a Node.js script with zero imports, and it just works. What is actually handling that HTTP request under the hood, and how would you prove it's not some other, unrelated HTTP client?"

What a strong answer should cover:

  • πŸ“Œ Interview term: undici β€” a real, from-scratch HTTP/1.1 client built specifically for Node.js by the Node.js project itself (not a wrapper around the older http/https modules) β€” genuinely faster and more spec-compliant than Node's legacy HTTP client internals for many real workloads.
  • πŸ“Œ Verified, not assumed β€” direct, internal proof: undici publishes real diagnostics_channel events (undici:request:create, undici:client:sendHeaders, undici:request:headers) during a real HTTP request β€” subscribing to those channels and then calling the global fetch() genuinely fired all three real events, direct, internal confirmation that fetch() is implemented via undici, not a separate, unrelated client.
  • The real, honest, verified nuance: require("node:undici") itself genuinely threw a real ERR_UNKNOWN_BUILTIN_MODULE on this Node version β€” confirmed via web search: Node's bundled-in undici (the one powering the global fetch) is not separately importable as node:undici on every Node version; the standalone undici npm package (installed separately) provides direct access to its fuller API (a real, configurable Agent, connection pooling, a MockAgent for tests) beyond what the global fetch alone exposes.
  • A precise answer names why Node adopted a purpose-built client rather than implementing fetch on the pre-existing http/https modules: those legacy modules were originally designed years before fetch's Web-standard semantics existed, and building undici from scratch, spec-compliant with the WHATWG Fetch/Streams standards from the ground up, was genuinely more direct than retrofitting decades-old internals to match a browser-originated API.
  • A precise answer scopes what fetch() alone does not expose that the full undici package does: real, fine-grained connection-pool tuning, a real Agent with configurable keep-alive/pipelining behavior, and real request/response interceptors β€” a precise answer names these as reasons to reach for the separate undici package directly, rather than assuming the global fetch alone covers every real, advanced HTTP-client need.

Clarifying questions expected:

  • "Does this specific use case need anything beyond what the plain global fetch() API already exposes β€” connection-pool tuning, interceptors, a MockAgent for tests β€” that would justify installing and importing the separate undici package directly?"
  • "Is the target Node version's global fetch implementation confirmed to be Stable in the actual deployed version, given it moved from experimental to Stable across recent Node releases?"

Code / implementation expected: Yes β€” real, internal proof that a plain global fetch() call genuinely fires undici's own diagnostics_channel events is the concrete, convincing proof of exactly what's handling the request under the hood, beyond simply citing documentation.

  • nodejs
  • http
  • undici
  • fetch

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

7 min read Β· 8 sections

Read the code

JavaScriptReal internal proof that global fetch() is undici-backed: subscribing to undici's own diagnostics_channel events
const dc = require("node:diagnostics_channel");
const http = require("node:http");

// undici publishes events under the "undici:*" diagnostics_channel namespace.
// if global fetch() triggers these, that's real, direct proof fetch is
// implemented via undici internally.
const seen = [];
for (const name of ["undici:request:create", "undici:client:sendHeaders", "undici:request:headers"]) {
  dc.subscribe(name, () => seen.push(name));
}

const server = http.createServer((req, res) => {
  res.writeHead(200, { "content-type": "application/json" });
  res.end(JSON.stringify({ ok: true, path: req.url }));
});

server.listen(0, async () => {
  const port = server.address().port;
  const res = await fetch(`http://localhost:${port}/hello`); // a PLAIN global fetch call
  console.log(await res.json()); // { ok: true, path: '/hello' }
  console.log(seen); // ['undici:request:create', 'undici:client:sendHeaders', 'undici:request:headers']
  server.close();
});

// --- separately, on this Node version, the bundled undici is NOT importable directly ---
try {
  require("node:undici");
} catch (err) {
  console.log(err.code); // ERR_UNKNOWN_BUILTIN_MODULE
  // -> install the standalone "undici" npm package for direct Agent/MockAgent access instead
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 98 of 152 in Node.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Node.js questions
Up nextMedium

What is `node:sqlite`, Node's built-in SQLite module?

Continue