# undici: Node.js's HTTP/1.1 client and what it changes for fetch users

> undici is an HTTP/1.1 client written from scratch for Node.js and the engine behind the built-in fetch. This review covers the request, pipeline, stream and dispatch APIs, what installing the npm package buys over the bundled version, and where the documentation stops short.

**nodejs/undici** — An HTTP/1.1 client, written from scratch for Node.js

- Repository: https://github.com/nodejs/undici
- Website: https://undici.nodejs.org/
- Stars: 7,707 · Forks: 896
- Language: JavaScript
- License: MIT
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/nodejs-undici

## The problem undici solves in a Node.js process

Node.js ships an HTTP client in its core `http` module, and for years most applications reached for a wrapper on top of it. The README positions undici as something different: an HTTP/1.1 client written from scratch for Node.js, not a layer over the core module. That distinction matters when you care about connection reuse, because the client owns the socket lifecycle rather than inheriting it.

The project is for Node.js developers who make outbound HTTP calls at volume: API clients, crawlers, service-to-service traffic, anything where per-request overhead shows up in a profile. It is also for anyone who has noticed that `fetch` is global in Node.js v18 and later and wants to know what is actually underneath. The README answers that directly: the built-in fetch is powered by a bundled version of undici, and `process.versions.undici` reports which one.

The name is not a clue to behaviour. The README explains it as Italian for eleven, following 1.1 to 11 to eleven to undici, and notes it is also a Stranger Things reference.

## Four APIs over one connection pool

The README's benchmark tables name four undici entry points: fetch, pipeline, request, stream, and dispatch, plus the Agent that owns the pool. In the HTTP/1.1 table they are ordered by throughput, with dispatch fastest and fetch slowest among the undici rows. The same ordering holds in the HTTPS and HTTP/2 tables. That ordering is the design story: each API gives you more control over the same underlying machinery and less of the Web platform's abstraction.

`request` returns an object with `statusCode`, `headers` and `body`, and the README's example calls `body.json()` on it. That is a different shape from the Response object fetch returns, and it is the reason people who want performance reach past fetch.

`Agent` is where connection behaviour lives. The README shows `new Agent({ keepAliveTimeout: 10000 })` passed to `setGlobalDispatcher`, which makes the agent the default for subsequent fetch calls. So the pooling configuration is separable from the call site: you set a dispatcher once and every request through it shares the same connections.

The benchmark methodology is stated: 50 TCP connections with a pipelining depth of 10, on Node 24.14.1, using a getting-data example. Separate scripts exist for HTTPS and HTTP/2, `benchmark-https.js` and `benchmark-http2.js`. The numbers are the project's own, run against specific servers, and they should be read as the project's measurements rather than as a prediction for your workload.

## Installing undici and making a first request

The README gives one install command. It is a normal npm package with no build step for consumers.

```bash
npm i undici
```

After that, the README's own example imports `request`, `fetch`, `Agent` and `setGlobalDispatcher` from the package. The `request` call returns `statusCode`, `headers` and `body`, and `body.json()` parses the payload. If you have used fetch, the destructuring is the part that will feel unfamiliar.

```js
import { request, fetch, Agent, setGlobalDispatcher } from 'undici';

const { statusCode, headers, body } = await request('https://api.example.com/data');
const data = await body.json();
```

To configure pooling, the README constructs an Agent with a `keepAliveTimeout` and installs it as the global dispatcher. Every fetch call after this line goes through that agent.

```js
const agent = new Agent({ keepAliveTimeout: 10000 });
setGlobalDispatcher(agent);
const response = await fetch('https://api.example.com/data');
```

One thing worth checking before you write any code: run `console.log(process.versions.undici)` and compare it with the version you are about to install. If you install the package, your imports come from the package, not from the bundled copy, and the two can differ.

## Where the bundled fetch and the installed package diverge

The README devotes a section to this comparison, and it is the most useful part of the document for anyone deciding whether to add a dependency. The built-in fetch requires nothing extra, works across JavaScript runtimes, handles gzip, deflate and br automatically, and has caching support described as in development. Those are real advantages.

The costs it lists are more interesting. The built-in fetch is limited to the undici version bundled with your Node.js release, so a fix that lands upstream may not reach you until you upgrade Node.js. It offers less control over connection pooling and advanced features. Its error handling follows Web API standards, meaning errors arrive wrapped in `TypeError`, which is awkward when you want to distinguish a DNS failure from a timeout from a TLS problem. And the README attributes a performance overhead to the Web Streams implementation.

Installing the package is the escape hatch from all four. You get the latest features and bug fixes, and access to the advanced APIs. The trade is a dependency you now track yourself, and an API surface that is wider than the one most applications need.

## Limits, and the cases where undici is the wrong pick

The clearest boundary is in the project's own description: it is an HTTP/1.1 client. The README publishes HTTP/2 benchmark results, so HTTP/2 is exercised, but the one-line description on the package and the repository does not claim it. If your service depends on HTTP/2 specifics, treat the HTTP/2 support as something to verify against the docs rather than something the project advertises.

The second limit is documentation depth. The README shows `request`, `fetch`, `Agent` and `setGlobalDispatcher`, and then stops. The `pipeline`, `stream` and `dispatch` APIs appear in the benchmark tables as names and nowhere else in the README, so the fastest paths are the least explained in the front page. A repository `docs/` directory exists, and that is where a reader has to go. The README does not document rollback or version pinning guidance either.

The third is the error model, which cuts both ways. Web API error semantics make the built-in fetch portable and make undici's own errors less informative at the call site. If your error handling is built around inspecting causes and codes, the fetch-style wrapping works against you. And if you are already on a runtime whose fetch is undici-based and you only make a handful of requests, adding the package buys you version currency and little else.

## undici against axios and the built-in fetch

The README's HTTP/1.1 table includes axios as a row, and the difference it shows is one of approach rather than of API taste. Axios is a request library that runs on multiple runtimes and sits on top of whatever HTTP primitive the platform provides. undici is the primitive for Node.js, written from scratch, with its own connection pool and its own dispatcher abstraction. That is why the table can list `undici - dispatch` and `undici - request` as separate rows: they are different levels of the same client, not different libraries.

The more consequential comparison is undici against the fetch that Node.js already gives you, because they are not competitors. The README states that the built-in fetch is powered by a bundled undici. Choosing between them is choosing between a frozen copy and a tracked dependency. If you need the Agent's `keepAliveTimeout` control, or the request API's `statusCode` and `body` shape, or a fix newer than your Node.js release, you install the package. If you need nothing beyond a standard fetch and you value having no dependency, the bundled copy is already there.

## Licence and the cost of tracking a fast-moving client

undici is MIT licensed, stated in both the README badge set and package.json. MIT is permissive and imposes no copyleft obligation on your application. This is not legal advice; if your organisation has a policy on dependency licences, the file to read is LICENSE in the repository root.

The maintenance signal is straightforward. The repository is not archived, and the last push was on 2026-09-21. Three release lines were published on 2026-09-04: v8.10.2, v7.29.1 and v6.28.1. That pattern, three maintained major lines receiving releases on the same day, tells you the project carries backports rather than moving only forward. For a team on an older major, that is the difference between a security fix and a forced upgrade.

The upgrade cost sits with you. If you install the package, you own the version in package.json and the schedule for moving it. If you rely on the bundled copy, your upgrade cadence is your Node.js upgrade cadence, which is slower but requires no decision. The repository has a SECURITY.md, which is where vulnerability reports are directed.

## Conclusion

Adopt undici when you need connection pooling control, the dispatch and pipeline APIs, or a newer version than the one bundled with your Node.js release. Stay with the built-in fetch if you want zero dependencies and Web API error semantics. Before committing, check process.versions.undici against the npm version you intend to install, and read the docs for the dispatcher API you plan to use, because the README only sketches request, fetch and Agent.

## FAQ

### What does undici mean?

The README states that undici means eleven in Italian, following the logic 1.1 to 11 to eleven to undici, and notes it is also a Stranger Things reference.

### What is the undici package used for?

It is an HTTP/1.1 client written from scratch for Node.js. The README positions it as an alternative to the core http module and as the engine behind the built-in fetch, with request, pipeline, stream and dispatch APIs over a shared connection pool.

### How does undici compare with axios?

The README's HTTP/1.1 benchmark table lists axios alongside several undici entry points, with undici's dispatch, stream, request and pipeline rows above it and undici fetch below it. The structural difference is that axios sits on top of a platform HTTP primitive while undici is the primitive itself, with its own pool and dispatcher.

### How does undici compare with fetch?

The built-in fetch in Node.js v18 and later is powered by a bundled version of undici, so they are the same client at different versions. The README lists the bundled copy's limits as being tied to your Node.js version, less control over connection pooling, errors wrapped in TypeError, and overhead from the Web Streams implementation.

## Sources

- [License: MIT](https://github.com/nodejs/undici/blob/main/LICENSE)
- [nodejs/undici on GitHub](https://github.com/nodejs/undici)
- [Project website](https://undici.nodejs.org/)
- [README](https://github.com/nodejs/undici/blob/main/README.md)
- [Releases](https://github.com/nodejs/undici/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nodejs-undici
