Needle
======
[![NPM](https://nodei.co/npm/needle.png)](https://nodei.co/npm/needle/)
[![npm downloads](https://img.shields.io/npm/dm/needle.svg?style=flat)](http://npm-stat.com/charts.html?package=needle)
![Build status](https://github.com/tomas/needle/actions/workflows/nodejs.yml/badge.svg)
The leanest and most handsome HTTP client in the Nodelands.
```js
var needle = require('needle');
needle.get('http://www.google.com', function(error, response) {
if (!error && response.statusCode == 200)
console.log(response.body);
});
```
Callbacks not floating your boat? Needle got your back.
``` js
var data = {
file: '/home/johnlennon/walrus.png',
content_type: 'image/png'
};
// the callback is optional, and needle returns a `readableStream` object
// that triggers a 'done' event when the request/response process is complete.
needle
.post('https://my.server.com/foo', data, { multipart: true })
.on('readable', function() { /* eat your chunks */ })
.on('done', function(err) {
console.log('Ready-o!');
})
```
From version 2.0.x up, Promises are also supported. Just call `needle()` directly and you'll get a native Promise object.
```js
needle('put', 'https://hacking.the.gibson/login', { password: 'god' }, { json: true })
.then(function(response) {
return doSomethingWith(response)
})
.catch(function(err) {
console.log('Call the locksmith!')
})
```
With only two real dependencies, Needle supports:
- HTTP/HTTPS requests, with the usual verbs you would expect
- All of Node's native TLS options, such as 'rejectUnauthorized' (see below)
- Basic & Digest authentication with auto-detection
- Multipart form-data (e.g. file uploads)
- HTTP Proxy forwarding, optionally with authentication
- Streaming gzip, deflate, and brotli decompression
- Automatic XML & JSON parsing
- 301/302/303 redirect following, with fine-grained tuning, and
- Streaming non-UTF-8 charset decoding, via `iconv-lite`
And yes, Mr. Wayne, it does come in black.
This makes Needle an ideal alternative for performing quick HTTP requests in Node, either for API interaction, downloading or uploading streams of data, and so on. If you need OAuth, AWS support or anything fancier, you should check out mikeal's request module.
Install
-------
```
$ npm install needle
```
Usage
-----
```js
// using promises
needle('get', 'https://server.com/posts/123')
.then(function(resp) {
// ...
})
.catch(function(err) {
// ...
});
// with callback
needle.get('ifconfig.me/all.json', function(error, response, body) {
if (error) throw error;
// body is an alias for `response.body`,
// that in this case holds a JSON-decoded object.
console.log(body.ip_addr);
});
// no callback, using streams
needle.get('https://google.com/images/logo.png')
.pipe(fs.createWriteStream('logo.png'))
.on('done', function(err) {
console.log('Pipe finished!');
});
```
As you can see, you can use Needle with Promises or without them. When using Promises or when a callback is passed, the response's body will be buffered and written to `response.body`, and the callback will be fired when all of the data has been collected and processed (e.g. decompressed, decoded and/or parsed).
When no callback is passed, however, the buffering logic will be skipped but the response stream will still go through Needle's processing pipeline, so you get all the benefits of post-processing while keeping the streamishness we all love from Node.
Response pipeline
-----------------
Depending on the response's 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.
You can also request a gzip/deflated/brotli response, which, if sent by the server, will be processed before parsing or decoding is performed. (Note: brotli is only supported on Node 10.16.0 or above, and will not be requested or processed on earlier versions.)
```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
});
```
Or in anti-callback mode, using a few other options:
```js
var options = {
compressed : true, // sets 'Accept-Encoding' to 'gzip, deflate, br'
follow_max : 5, // follow up to five redirects
rejectUnauthorized : true // verify SSL certificate
}
var stream = needle.get('https://backend.server.com/everything.html', options);
// read the chunks from the 'readable' event, so the stream gets consumed.
stream.on('readable', function() {
while (data = this.read()) {
console.log(data.toString());
}
})
stream.on('done', function(err) {
// if our request had an error, our 'done' event will tell us.
if (!err) console.log('Great success!');
})
```
API
---
### needle(method, url[, data][, options][, callback]) `(> 2.0.x)`
Calling `needle()` directly returns a Promise. Besides `method` and `url`, all parameters are optional, although when sending a `post`, `put` or `patch` request you will get an error if `data` is not present.
```js
needle('get', 'http://some.url.com')
.then(function(resp) { console.log(resp.body) })
.catch(function(err) { console.error(err) })
```
Except from the above, all of Needle's request methods return a Readable stream, and both `options` and `callback` are optional. If passed, the callback will return three arguments: `error`, `response` and `body`, which is basically an alias for `response.body`.
### needle.head(url[, options][, callback])
```js
needle.head('https://my.backend.server.com', {
open_timeout: 5000 // if we're not able to open a connection in 5 seconds, boom.
}, function(err, resp) {
if (err)
console.log('Shoot! Something is wrong: ' + err.message)
else
console.log('Yup, still alive.')
})
```
### needle.get(url[, options][, callback])
```js
needle.get('google.com/search?q=syd+barrett', function(err, resp) {
// if no http:// is found, Needle will automagically prepend it.
});
```
### needle.post(url, data[, options][, callback])
```js
var options = {
headers: { 'X-Custom-Header': 'Bumbaway atuna' }
}
needle.post('https://my.app.com/endpoint', 'foo=bar', options, function(err, resp) {
// you can pass params as a string or as an object.
});
```
### needle.put(url, data[, options][, callback])
```js
var nested = {
params: {
are: {
also: 'supported'
}
}
}
needle.put('https://api.app.com/v2', nested, function(err, resp) {
console.log('Got ' + resp.bytes + ' bytes.') // another nice treat from this handsome fella.
});
```
### needle.patch(url, data[, options][, callback])
Same behaviour as PUT.
### needle.delete(url, data[, options][, callback])
```js
var options = {
username: 'fidelio',
password: 'x'
}
needle.delete('https://api.app.com/messages/123', null, options, function(err, resp) {
// in this case, data may be null, but you need to explicity pass it.
});
```
### needle.request(method, url, data[, options][, callback])
Generic request. This not only allows for flexibility, but also lets you perform a GET request with data, in which case will be appended to the request as a query string, unless you pass a `json: true` option (read below).
```js
var params = {
q : 'a very smart query',
page : 2
}
needle.request('get', 'forum.com/search', params, function(err, resp) {
if (!err && resp.statusCode == 200)
console.log(resp.body); // here you go, mister.
});
```
Now, if you set pass `json: true` among the options, Needle won't set your params as a querystring but instead send a JSON representation of your data through the request's body, as well as set the `Content-Type` and `Accept` headers to `application/json`.
```js
needle.request('get', 'forum.com/search', params, { json: true }, function(err, resp) {
if (resp.statusCode == 200) console.log('It worked!');
});
```
Events
------
The [Readable stream](https://nodejs.org/api/stream.html#stream_class_stream_readable) object returned by the above request methods emits the following events, in addition to the regular ones (e.g. `end`, `close`, `data`, `pipe`, `readable`).
### Event: `'response'`
- `response `
Emitted when the underlying [http.ClientRequest](https://nodejs.org/api/http.html#http_class_http_clientrequest) emits a response event. This is after the connection is established and the header received, but before any of it is processed (e.g. authorization required or redirect to be followed). No data has been consumed at this point.
### Event: `'redirect'`
- `location `
Indicates that the a redirect is being followed. This means that the response code was a redirect (`301`, `302`, `303`, `307`) and the given [redirect options](#redirect-options) allowed following the URL received in the `Location` header.
### Event: `'header'`
- `statusCode `
- `headers