# needle is a Node HTTP client you can consume three different ways

> The needle package is a small HTTP client for Node.js with only two runtime dependencies, and its design bet is that the same call should work as a promise, as a callback or as a readable stream. It also does the post-processing that usually has to be written by hand, parsing JSON and XML, decompressing responses and decoding non UTF-8 charsets on the way through.

**tomas/needle** — Nimble, streamable HTTP client for Node.js. With proxy, iconv, cookie, deflate & multipart support.

- Repository: https://github.com/tomas/needle
- Website: https://www.npmjs.com/package/needle
- Stars: 1,637 · Forks: 235
- Language: JavaScript
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/tomas-needle

## Two dependencies, and a command line client

The package metadata for needle is short, and the dependency list is the headline. There are two runtime dependencies, iconv-lite at 0.6.3 and sax at 1.2.4, which is the streaming XML parser. Everything else, the TLS handling, the redirects, the proxying and the multipart encoding, is built on Node's own http and https modules.

That matters more than a dependency count usually does. An HTTP client is exactly the kind of package where a transitive dependency chain turns into a supply chain problem, and two well known packages is a short enough list to read.

The readme describes needle as the leanest and most handsome HTTP client in the Nodelands, and calls it an ideal alternative for performing quick HTTP requests in Node, either for API interaction, or for downloading or uploading streams of data.

There is also a command line client, which the readme does not mention in the body but the package metadata records, with a bin entry pointing at ./bin/needle and the main entry at ./lib/needle. So the same library that you use from your application can be driven from a shell, which is handy for the quick call you need while debugging.

The author is Tomás Pollak, and the licence is MIT in license.txt. The engines field asks for Node 4.4.x or later, which is a deliberately low floor and tells you something about how little the package relies on modern runtime features, apart from the brotli support that needs Node 10.16.0.

The audience is a Node developer who wants a request library with the response already cleaned up, and who does not want to assemble that cleanup out of separate packages.

## One call shape, three ways to consume the answer

The API section documents the signature as needle(method, url[, data][, options][, callback]) for versions above 2.0.x, and the point of it is that the shape does not change with the style you want.

Call needle directly and you get a native Promise:

```js
needle('get', 'http://some.url.com')
  .then(function(resp) { console.log(resp.body) })
  .catch(function(err) { console.error(err) })
```

Pass a callback and you get the classic Node shape, where the callback receives error, response and body, and body is an alias for response.body.

```js
needle.get('ifconfig.me/all.json', function(error, response, body) {
  if (error) throw error;
  console.log(body.ip_addr);
});
```

And pass neither, and the request method returns a Readable stream that you can pipe:

```js
needle.get('https://google.com/images/logo.png')
  .pipe(fs.createWriteStream('logo.png'))
  .on('done', function(err) {
    console.log('Pipe finished!');
  });
```

The documentation is explicit about the difference between the last two. When using promises or when a callback is passed, the response body will be buffered and written to response.body, and the callback fires when all the data has been collected and processed, for example decompressed, decoded and parsed. When no callback is passed, the buffering logic is skipped, but the response stream still goes through the processing pipeline, so you get the benefits of post-processing while keeping the streamishness.

That last sentence is the most useful line in the readme, because it is what makes the streaming mode worth having rather than merely different.

## The response pipeline is the reason to use it

The response pipeline section describes what happens to a response before you see it.

Depending on the response Content-Type, needle will either attempt to parse JSON or XML streams, or, if a text response was received, will ensure that the final encoding you get is UTF-8.

So the three things you would otherwise write are built in. JSON is parsed, XML is parsed with sax, and a text response that arrives in a legacy charset is decoded into UTF-8 rather than handed to you as mojibake. The decode step is what the iconv-lite dependency is for, and it is a streaming decode, so it works on the same path as the parsers.

Compression is handled in the same place. You can request a gzip, deflated or brotli response, and if the server sends it, it is processed before parsing or decoding is performed. The readme notes that brotli is only supported on Node 10.16.0 or above and will not be requested or processed on earlier versions.

The example shows the option and the effect:

```js
needle.get('http://stackoverflow.com/feeds', { compressed: true }, function(err, resp) {
  console.log(resp.body); // this little guy won't be a Gzipped binary blob
                          // but a nice object containing all the latest entries
});
```

The compressed option sets the Accept-Encoding header to gzip, deflate and br, and the server response is then transparently undone before you see the body.

This is the layer that separates needle from a thin wrapper around Node's http module. If you have written that wrapper yourself, you have written the decompress, the decode and the parse steps, and you have probably got at least one of them subtly wrong for a server that returns a gzipped ISO-8859-1 XML feed. That is the case needle is for.

## The options that matter, and the ones you will actually use

The readme lists what needle supports, and the list is worth reading as a specification of the failure modes it handles for you.

There are HTTP and HTTPS requests with the usual verbs, and all of Node's native TLS options such as rejectUnauthorized, which appears in the examples. There is Basic and Digest authentication with auto-detection, so you do not have to know in advance which one the server wants. There is multipart form-data for file uploads, HTTP proxy forwarding with optional authentication, and streaming gzip, deflate and brotli decompression. There is automatic XML and JSON parsing, redirect following for 301, 302 and 303 with fine-grained tuning, streaming non-UTF-8 charset decoding via iconv-lite, and the ability to abort any or all needle requests using AbortSignal objects.

The options that show up in the examples are the practical ones. follow_max sets how many redirects to follow, and the readme shows it at five. rejectUnauthorized verifies the SSL certificate, and it is on by default in the example, which is the right default. compressed requests the compressed encodings. open_timeout is the connection timeout, shown as 5000 milliseconds with the comment that if the connection is not open in five seconds, boom. And the headers option is a plain object of request headers.

The multipart example shows the shape of the payload, where the data object carries a file path and a content type:

```js
var data = {
  file: '/home/johnlennon/walrus.png',
  content_type: 'image/png'
};
```

And the request is made with the multipart option set, which returns a readable stream that emits a done event when the request and response process is complete.

One behaviour worth knowing from the API section: for a post, put or patch request you get an error if data is not present, so the optional-looking signature is not entirely optional.

## A small surface, tested with mocha, with examples for the awkward parts

The repository is a small Node project. There is a lib directory for the implementation, a bin directory for the command line client, a test directory, and an examples directory whose file names are a good index of what the library is for: deflated-stream.js, digest-auth.js, download-to-file.js, multipart-stream.js, parsed-stream.js, parsed-stream2.js, stream-events.js, stream-to-file.js, upload-image.js, and a stream-multiple directory.

That set of examples is instructive because it covers the cases that are awkward rather than the happy path. Digest authentication, deflated responses, multipart uploads, streaming to a file, and parsed streams with two variants are all the things that break when you write a request wrapper from scratch.

The test setup is minimal and old, which is consistent with the rest of the project. The test script is mocha test, and the dev dependencies are mocha at 5.2.0, should for assertions, sinon for stubs, q for promises, pump for streams, jsx, and JSONStream and xml2js and jschardet for comparison work in the tests. There is a .editorconfig and an .editorconfig directory listing with the usual metadata, and a package-lock.json.

The licence is MIT in license.txt.

So the project presents itself as a finished, small library with a stable API rather than an actively evolving one, and the examples are the documentation for the parts where the behaviour is not obvious from the signature.

## The version gap between the manifest and the last tag

The maintenance picture is the thing to check before you commit to it, and it is a little unusual.

The repository is not archived, and the last push was on 2026-07-01. The tagged releases, though, are v3.0.0 and v3.1.0 on 2022-04-07, and v3.2.0 on 2022-11-17. Meanwhile the package metadata says version 3.4.1.

So the code on npm is ahead of the last GitHub release tag by a long way, and the repository itself is being touched. That combination usually means releases are cut from the branch without a tag for every one, and it means the release list on the repository is not a reliable picture of what you would install. The npm page is the one to read.

There is a related detail. The readme mentions that from version 2.0.x up, promises are also supported, and the API section marks the direct call signature as being for versions above 2.0.x, so the promise interface has been stable across the whole 3.x line. That is good news for anyone reading older examples, and it is the main compatibility fact you can extract from the documentation.

The engines field asking for Node 4.4.x and later, and the brotli caveat about Node 10.16.0, tell you the library has kept its floor deliberately low, which is friendly to older runtimes and slightly awkward on modern ones where fetch is the built in answer.

For an adopter, the practical checks are these. Read the npm page rather than the release list, confirm the installed version is one you want to pin, and run the example closest to your use case, because the stream semantics in particular are the part where a version difference would show up first.

## Where a different client would serve you better

There are two clear cases where needle is the wrong choice, and one where it is the only sensible one.

The first is when you want an HTTP/2 or fetch style interface. The API here is built around Node request streams, a callback or promise trio that predates the fetch standard, and a module that emits done events. If your team is writing new code against a modern Node runtime, the built in fetch or an undici style client gives you the same ground with a smaller mental model, and the ecosystem has moved that way.

The second is when your problem is not quick requests. Needle is described as ideal for quick HTTP requests, for API interaction and for downloading or uploading streams. A service that needs connection pooling across thousands of concurrent calls, or a client with a mature interceptor and retry ecosystem, is a different kind of dependency, and this is not it.

The third case is the one where needle is right. If you are maintaining an older service pinned to an older Node, or you want a request library whose entire source fits in a screen and has two dependencies, or you need Digest authentication with auto-detection and a non UTF-8 charset decoded correctly in one call, then this is a very good fit and there is little to replace it with.

The trade you are making is capability against currency. Needle gives you a complete, well documented pipeline with a small footprint, and it asks you to accept a library that was shaped for Node of a particular era.

## Conclusion

needle is worth adopting if you have a Node service that makes small to medium HTTP calls and you want the response already parsed, decompressed and decoded without writing that pipeline, because the two dependencies and the single call shape make it easy to adopt and easy to leave. It is the wrong choice if you need a modern HTTP/2 or fetch style interface, since the API is built around Node request streams, and it is the wrong choice if you want an actively maintained dependency, because the last tagged release is v3.2.0 from 2022-11-17 even though the manifest is at 3.4.1. Before you adopt it, check whether the version you install matches the Node version you run, and note that the last push was on 2026-07-01.

## FAQ

### How do I install needle and make a request?

Run npm install needle, then either call needle directly for a promise, for example needle('get', 'http://some.url.com'), or use a method such as needle.get with a callback, or omit the callback to receive a readable stream. When a callback or a promise is used the body is buffered into response.body, and without one the stream still goes through the processing pipeline.

### What does needle handle automatically in a response?

Depending on the Content-Type it parses JSON or XML streams, and for a text response it ensures the final encoding is UTF-8. Compressed responses are decompressed first, so the compressed option makes the body a parsed object rather than a binary blob. The charset decoding is streaming and uses iconv-lite.

### Which dependencies does needle have?

Two at runtime, iconv-lite for charset decoding and sax for streaming XML parsing. Everything else, TLS handling, redirects, proxying and multipart encoding, is built on Node's own http and https modules, and the engines field asks for Node 4.4.x or later.

### Does needle support file uploads and proxies?

Yes. Multipart form-data for uploads is supported, with a payload object carrying a file path and a content type, and the multipart option set on the request. HTTP proxy forwarding is supported, optionally with authentication, and requests can also be aborted with AbortSignal objects.

### What is the current version of needle and how active is the project?

The package metadata is at version 3.4.1, while the most recent tagged release is v3.2.0 from 2022-11-17, and the repository was pushed to on 2026-07-01. The project is not archived, but the release tags have not kept pace with the published version, so the npm page rather than the release list is the reliable place to check what you would install.

## Sources

- [License: MIT](https://github.com/tomas/needle/blob/master/LICENSE)
- [Project website](https://www.npmjs.com/package/needle)
- [README](https://github.com/tomas/needle/blob/master/README.md)
- [Releases](https://github.com/tomas/needle/releases)
- [tomas/needle on GitHub](https://github.com/tomas/needle)

---

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