Chainy is super light-weight http client for Node.js. Crafted for flexibilitya and readability.
chainy
.post('/api/user')
.set('X-API-Key', 'someapikey')
.set('Accept', 'application/json')
.send({ name: 'Alex', age: '20' })
.then(res => {
alert('got something:' + JSON.stringify(res.body));
});
or using async/await:
(async () => {
const response = await chainy.post('/api/user')
.set('X-API-Key', 'someapikey')
.set('Accept', 'application/json')
.send({ name: 'Alex', age: '20' })
console.log(chainy)
})();
A request can be initiated by invoking the appropriate method on the chainy
object, then calling .send()
to send the request. For example a simple GET request:
chainy
.get('/api')
.send()
.then(res => {
// res.body, res.headers, res.status
})
.catch(err => {
// err.message, err.response
});
Absolute URLs can be used. In web browsers absolute URLs work only if the server implements CORS.
chainy
.get('https://example.com/search')
.then(res => {
// res
});
Setting header fields is simple, invoke .set()
with name and value:
chainy
.get('/search')
.set('API-Key', 'someapikey')
.set('Accept', 'application/json')
.send(data);
Also, you can pass an object to set several fields in a single call:
chainy
.get('/search')
.set({ 'API-Key': 'someapikey', Accept: 'application/json' })
.send(data);
The .query()
method accepts objects, which when used with the GET method will form a query-string. The following will produce the path /api?name=Alex&age=20&order=desc
.
chainy
.get('/api')
.query({ name: 'Alex' })
.query({ age: '20' })
.query({ order: 'desc' })
.send(data);
Or as a single object:
chainy
.get('/api')
.query({ name: 'Alex', age: '20', order: 'desc' })
.send(data);
The .query()
method accepts strings as well:
chainy
.get('/api')
.query('name=Alex&age=19')
.send(data);
Or joined:
chainy
.get('/api')
.query('name=Alex')
.query('age=20')
.send(data);
You can also use the .query()
method for HEAD requests. The following will produce the path /users?email=sasha.parh@gmail.com
:
chainy
.head('/users')
.query({ email: 'sasha.parh@gmail.com' })
.send(data);
A typical JSON POST request, where we set the Content-Type header field appropriately, and "write" some data.
chainy
.post('/user')
.set('Content-Type', 'application/json')
.send('{"name":"Alex","age":"20"}');
Since JSON is undoubtedly the most common, it's the default! The following example is equivalent to the previous.
chainy
.post('/user')
.send(data);
By default sending strings will set the Content-Type
to application/x-www-form-urlencoded
:
chainy
.post('/user')
.send('name=Alex&age=20');
The obvious solution is to use the .set()
method:
chainy
.post('/api')
.set('Content-Type', 'application/json')
As a short-hand the .type()
method is also available, accepting
the canonicalized MIME type name complete with type/subtype, or
simply the extension name such as "xml", "json", "png", etc:
chainy.post('/user')
.type('form')
.send(formData);
chainy.post('/user')
.type('json')
.send(jsonData);
chainy.post('/user')
.type('png')
.send(pngData);
Chainy will automatically serialize objects to JSON, XML and forms using .type()
+ .serialize()
methods:
chainy.post('/user')
.type('json')
.serialize()
.send(objectData);
When given the .retry()
method, Chainy will automatically retry requests, if they fail in a way that is transient or could be due to a flaky Internet connection.
This method has two optional arguments: number of retries (default 1) and a callback. It calls callback(err, res)
before each retry. The callback may return true
/false
to control whether the request should be retried (but the maximum number of retries is always applied).
chainy
.get('https://example.com/search')
.retry(2) // or:
.retry(2, callback)
.send();
Use .retry()
only with requests that are idempotent (i.e. multiple requests reaching the server won't cause undesirable side effects like duplicate purchases).
All request methods are tried by default (which means if you do not want POST requests to be retried, you will need to pass a custom retry callback).
By default the following status codes are retried:
408
413
429
500
502
503
504
521
522
524
By default the following error codes are retried:
'ETIMEDOUT'
'ECONNRESET'
'EADDRINUSE'
'ECONNREFUSED'
'EPIPE'
'ENOTFOUND'
'ENETUNREACH'
'EAI_AGAIN'
You can deserialize data using .accept()
method. It`s allowing you to specify "xml", "json", "form".
chainy.get('/user')
.accept('json')
.send();
In Node.js Сhainy supports methods to configure HTTPS requests:
-
.ca()
: Set the CA certificate(s) to trust -
.cert()
: Set the client certificate chain(s) -
.key()
: Set the client private key(s) -
.pfx()
: Set the client PFX or PKCS12 encoded private key and certificate chain -
.disableTLSCerts()
: Does not reject expired or invalid TLS certs. Sets internallyrejectUnauthorized=true
. Be warned, this method allows MITM attacks.
const key = fs.readFileSync('key.pem'),
cert = fs.readFileSync('cert.pem');
chainy.post('/client-auth').key(key).cert(cert).send(data).then(callback);
const ca = fs.readFileSync('ca.cert.pem');
chainy
.post('https://localhost/private-ca-server')
.ca(ca)
.send(data)
.then((res) => {});
The Node client supports multipart/form-data via the Formidable module. When parsing multipart responses, the object res.files
is also available to you. Suppose for example a request responds with the following multipart body:
--whoop
Content-Disposition: attachment; name="image"; filename="tobi.png"
Content-Type: image/png
... data here ...
--whoop
Content-Disposition: form-data; name="name"
Content-Type: text/plain
Tobi
--whoop--
You would have the values res.body.name
provided as "Tobi", and res.files.image
as a File
object containing the path on disk, filename, and other properties.
Many helpful flags and properties are set on the Response
object, ranging from the response text, parsed response body, header fields, status flags and more.
Much like Сhainy can auto-serialize request data, it can also automatically parse it with
.accept
method. Object is available via res.body
.
The res.header
contains an object of parsed header fields, lowercasing field names much like node does. For example res.header['content-length']
.
The Content-Type response header is special-cased, providing res.type
, which is void of the charset (if any). For example the Content-Type of "text/html; charset=utf8" will provide "text/html" as res.type
, and the res.charset
property would then contain "utf8".
The response status flags help determine if the request was a success, among other useful information, making Chainy ideal for interacting with RESTful web services.
Sometimes networks and servers get "stuck" and never respond after accepting a request. Set timeouts to avoid requests waiting forever.
-
req.timeout({deadline:ms})
orreq.timeout(ms)
(wherems
is a number of milliseconds > 0) sets a deadline for the entire request (including all uploads, redirects, server processing time) to complete. If the response isn't fully downloaded within that time, the request will be aborted. -
req.timeout({response:ms})
sets maximum time to wait for the first byte to arrive from the server, but it does not limit how long the entire download can take. Response timeout should be at least few seconds longer than just the time it takes the server to respond, because it also includes time to make DNS lookup, TCP/IP and TLS connections, and time to upload request data.
You should use both deadline
and response
timeouts. This way you can use a short response timeout to detect unresponsive networks quickly, and a long deadline to give time for downloads on slow, but reliable, networks. Note that both of these timers limit how long uploads of attached files are allowed to take. Use long timeouts if you're uploading files.
chainy
.get('/big-file?network=slow')
.timeout({
response: 5000, // Wait 5 seconds for the server to start sending,
deadline: 60000, // but allow 1 minute for the file to finish loading.
})
.then(res => {
/* responded in time */
}, err => {
if (err.timeout) { /* timed out! */ } else { /* other error */ }
});
Timeout errors have a .timeout
property.
By default up to 5 redirects will be followed, however you may specify this with the res.redirects(n)
method:
const response = await request.get('/some.png').redirects(2);
Redirects exceeding the limit are treated as errors. Use .ok(res => res.status < 400)
to read them as successful responses.
The Node client allows you to pipe data to and from the request. Please note that .pipe()
is used instead of .send()
method.
For example piping a file's contents as the request:
const chainy = require('chainy-request');
const fs = require('fs');
const stream = fs.createReadStream('file/path/my.json');
const req = chainy.post('/somewhere').type('json');
stream.pipe(req);
Or piping the response to a file:
const stream = fs.createWriteStream('path/to/my.json');
const req = chainy.get('/some.json');
req.pipe(stream);
Chainy is also great for building multipart requests for which it provides methods .attach()
and .field()
.
When you use .field()
or .attach()
you can't use .send()
and you must not set Content-Type
(the correct type will be set for you).
To send a file use .attach(name, filePath)
. You can attach multiple files by calling .attach
multiple times. The arguments are:
-
name
— field name in the form. -
filePath
— either string with file path.chainy .post('/uploadSomtheing') .attach('image1', 'path/to/felix.jpeg') .attach('image2', 'luna.jpeg') .send();
For security reasons, browsers will block cross-origin requests unless the server opts-in using CORS headers. Browsers will also make extra OPTIONS requests to check what HTTP headers and methods are allowed by the server. Read more about CORS.
The .withCredentials()
method enables the ability to send cookies from the origin, however only when Access-Control-Allow-Origin
is not a wildcard ("*"), and Access-Control-Allow-Credentials
is "true".
chainy
.get('https://api.example.com:4001/')
.withCredentials()
.send()
.then(res => {
assert.equal(200, res.status);
assert.equal('tobi', res.text);
})