rekwest
Version:
The robust request library that humanity deserves 🌐
246 lines (199 loc) • 9.38 kB
Markdown
The robust request library that humanity deserves 🌐
---
This package provides highly likely functional and **easy-to-use** abstraction atop of
native [http(s).request](https://nodejs.org/api/https.html#httpsrequesturl-options-callback)
and [http2.request](https://nodejs.org/api/http2.html#clienthttp2sessionrequestheaders-options).
## Abstract
* Fetch-alike 🥏
* Cool-beans 🫐 config options (with defaults)
* Automatic HTTP/2 support (ALPN negotiation) 💼
* Automatic or opt-in body parse (with non-UTF-8 charset decoding) 🉑
* Automatic and simplistic `Cookies` treatment (with built-in **jar** & **ttl**) 🍪
* Automatic decompression (with opt-in body compression) 🗜️
* Built-in streamable `FormData` interface 🔌
* Support redirects & retries with fine-grained tune-ups 🪛
* Support all legit request body types (include blobs & streams) 📦
* Support both CJS and ESM module systems 🧩
* Fully promise-able and pipe-able 🔗
* Zero dependencies 🗽
## Prerequisites
* Node.js `>= 20.0.0`
## Installation
```bash
npm install rekwest --save
```
### Usage
```javascript
import rekwest, { constants } from 'rekwest';
const {
HTTP2_HEADER_AUTHORIZATION,
HTTP2_HEADER_CONTENT_ENCODING,
HTTP2_METHOD_POST,
HTTP_STATUS_OK,
} = constants;
const url = 'https://somewhe.re/somewhat/endpoint';
const res = await rekwest(url, {
body: { celestial: 'payload' },
headers: {
[HTTP2_HEADER_AUTHORIZATION]: 'Bearer [token]',
[HTTP2_HEADER_CONTENT_ENCODING]: 'br', // enables: body compression
/** [HTTP2_HEADER_CONTENT_TYPE]
* is undue for
* Array/Blob/File/FormData/Object/URLSearchParams body types
* and will be set automatically, with an option to override it here
*/
},
method: HTTP2_METHOD_POST,
});
console.assert(res.statusCode === HTTP_STATUS_OK);
console.info(res.headers);
console.log(res.body);
```
---
```javascript
import { Readable } from 'node:stream';
import rekwest, {
constants,
Blob,
File,
FormData,
} from 'rekwest';
const {
HTTP2_HEADER_AUTHORIZATION,
HTTP2_HEADER_CONTENT_ENCODING,
HTTP2_METHOD_POST,
HTTP_STATUS_OK,
} = constants;
const blob = new Blob(['bits']);
const file = new File(['bits'], 'file.dab');
const readable = Readable.from('bits');
const fd = new FormData({
aux: Date.now(), // either [[key, value]] or kv sequenceable
});
fd.append('celestial', 'payload');
fd.append('blob', blob, 'blob.dab');
fd.append('file', file);
fd.append('readable', readable, 'readable.dab');
const url = 'https://somewhe.re/somewhat/endpoint';
const res = await rekwest(url, {
body: fd,
headers: {
[HTTP2_HEADER_AUTHORIZATION]: 'Bearer [token]',
[HTTP2_HEADER_CONTENT_ENCODING]: 'zstd', // enables: body compression
},
method: HTTP2_METHOD_POST,
});
console.assert(res.statusCode === HTTP_STATUS_OK);
console.info(res.headers);
console.log(res.body);
```
### API
#### `rekwest(url[, options])`
* `url` **{string | URL}** The URL to send the request to
* `options` **{Object}**
Extends [http(s).RequestOptions](https://nodejs.org/api/https.html#httpsrequesturl-options-callback) along with
extra [http2.ClientSessionOptions](https://nodejs.org/api/http2.html#http2connectauthority-options-listener)
& [http2.ClientSessionRequestOptions](https://nodejs.org/api/http2.html#clienthttp2sessionrequestheaders-options)
and [tls.ConnectionOptions](https://nodejs.org/api/tls.html#tlsconnectoptions-callback)
for HTTP/2 attunes
* `baseURL` **{string | URL}** The base URL to use in cases where `url` is a relative URL
* `body` **{string | Array | ArrayBuffer | ArrayBufferView | AsyncIterator | Blob | Buffer | DataView | File |
FormData | Iterator | Object | Readable | ReadableStream | SharedArrayBuffer | URLSearchParams}** The body to send
with the request
* `cookies` **{boolean | Array<[k, v]> | Array<string\> | Cookies | Object | URLSearchParams}** `Default: true` The
cookies to add to
the request
* `cookiesTTL` **{boolean}** `Default: false` Controls enablement of TTL for the cookies cache
* `credentials` **{include | omit | same-origin}** `Default: same-origin` Controls credentials in case of cross-origin
redirects
* `digest` **{boolean}** `Default: true` Controls whether to read the response stream or simply add a mixin
* `follow` **{number}** `Default: 20` The number of redirects to follow
* `h2` **{boolean}** `Default: false` Forces the use of HTTP/2 protocol
* `headers` **{Object}** The headers to add to the request
* `maxRetryAfter` **{number}** The upper limit of `retry-after` header. If unset, it will use `timeout` value
* `parse` **{boolean}** `Default: true` Controls whether to parse response body or simply return a buffer
* `redirect` **{error | follow | manual}** `Default: follow` Controls the redirect flows
* `retry` **{Object}** Represents the retry options
* `attempts` **{number}** `Default: 0` The number of retry attempts
* `backoffStrategy` **{string}** `Default: interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)`
The backoff strategy algorithm that increases logarithmically. To fixate set value to `interval * 1`
* `errorCodes` **{string[]}**
`Default: ['EAI_AGAIN', 'ECONNREFUSED', 'ECONNRESET', 'EHOSTDOWN', 'EHOSTUNREACH', 'ENETDOWN', 'ENETUNREACH', 'ENOTFOUND', 'EPIPE', 'ERR_HTTP2_STREAM_ERROR']`
The list of error codes to retry on
* `interval` **{number}** `Default: 1e3` The initial retry interval
* `retryAfter` **{boolean}** `Default: true` Controls `retry-after` header receptiveness
* `statusCodes` **{number[]}** `Default: [429, 500, 502, 503, 504]` The list of status codes to retry on
* `stripTrailingSlash` **{boolean}** `Default: false` Controls whether to strip trailing slash at the end of the URL
* `thenable` **{boolean}** `Default: false` Controls the promise resolutions
* `timeout` **{number}** `Default: 3e5` The number of milliseconds a request can take before termination
* `trimTrailingSlashes` **{boolean}** `Default: false` Controls whether to trim trailing slashes within the URL
* **Returns:** Promise that resolves to
extended [http.IncomingMessage](https://nodejs.org/api/http.html#class-httpincomingmessage)
or [http2.ClientHttp2Stream](https://nodejs.org/api/http2.html#class-clienthttp2stream) which is respectively
readable and duplex streams
* if `digest: true` & `parse: true`
* `body` **{string | Array | Buffer | Object}** The body based on its content type
* if `digest: false`
* `arrayBuffer` **{AsyncFunction}** Reads the response and returns **ArrayBuffer**
* `blob` **{AsyncFunction}** Reads the response and returns **Blob**
* `body` **{AsyncFunction}** Reads the response and returns **Buffer** if `parse: false`
* `bytes` **{AsyncFunction}** Reads the response and returns **Uint8Array**
* `json` **{AsyncFunction}** Reads the response and returns **Object**
* `text` **{AsyncFunction}** Reads the response and returns **String**
* `bodyUsed` **{boolean}** Indicates whether the response were read or not
* `cookies` **{undefined | Cookies}** The cookies sent and received with the response
* `headers` **{Object}** The headers received with the response
* `httpVersion` **{string}** Indicates protocol version negotiated with the server
* `ok` **{boolean}** Indicates if the response was successful (statusCode: **200-299**)
* `redirected` **{boolean}** Indicates if the response is the result of a redirect
* `statusCode` **{number}** Indicates the status code of the response
* `trailers` **{undefined | Object}** The trailer headers received with the response
---
#### `rekwest.defaults`
The object to fulfill with default [options](#rekwesturl-options).
---
#### `rekwest.extend(options)`
The method to extend default [options](#rekwesturl-options) per instance.
```javascript
import rekwest, { constants } from 'rekwest';
const {
HTTP_STATUS_OK,
} = constants;
const rk = rekwest.extend({
baseURL: 'https://somewhe.re',
});
const signal = AbortSignal.timeout(1e4);
const url = '/somewhat/endpoint';
const res = await rk(url, {
signal,
});
console.assert(res.statusCode === HTTP_STATUS_OK);
console.info(res.headers);
console.log(res.body);
```
---
#### `rekwest.stream(url[, options])`
The method with limited functionality to use with streams and/or pipes.
* No automata (redirects & retries)
* Pass `h2: true` in options to use HTTP/2 protocol
* Use `ackn({ url: URL })` method in advance to check the available protocols
```javascript
import fs from 'node:fs';
import { pipeline } from 'node:stream/promises';
import rekwest, {
ackn,
constants,
} from 'rekwest';
const {
HTTP2_METHOD_POST,
} = constants;
const url = new URL('https://somewhe.re/somewhat/endpoint');
const options = await ackn({ url });
await pipeline(
fs.createReadStream('/path/to/read/inlet.xyz'),
rekwest.stream(url, { ...options, method: HTTP2_METHOD_POST }),
fs.createWriteStream('/path/to/write/outlet.xyz'),
);
```
---
For more details, please check tests (coverage: **>97%**) in the repository.