{"id":18539047,"url":"https://github.com/simov/request-compose","last_synced_at":"2025-04-09T06:11:33.653Z","repository":{"id":28853767,"uuid":"119522394","full_name":"simov/request-compose","owner":"simov","description":"Composable HTTP Client","archived":false,"fork":false,"pushed_at":"2024-09-16T18:05:54.000Z","size":222,"stargazers_count":89,"open_issues_count":2,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-02T02:11:57.755Z","etag":null,"topics":["client","fp","functional-programming","http","http-client","javascript","js","node","nodejs"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/simov.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-01-30T10:45:29.000Z","updated_at":"2025-01-22T11:03:06.000Z","dependencies_parsed_at":"2024-11-06T19:48:00.891Z","dependency_job_id":"881a6987-3f68-4c92-9e89-0e9229d9c876","html_url":"https://github.com/simov/request-compose","commit_stats":{"total_commits":169,"total_committers":3,"mean_commits":"56.333333333333336","dds":"0.011834319526627168","last_synced_commit":"65f521a7656f3d62b68b6309a0c195e8d9fd8037"},"previous_names":[],"tags_count":35,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simov%2Frequest-compose","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simov%2Frequest-compose/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simov%2Frequest-compose/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simov%2Frequest-compose/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/simov","download_url":"https://codeload.github.com/simov/request-compose/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247987285,"owners_count":21028895,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["client","fp","functional-programming","http","http-client","javascript","js","node","nodejs"],"created_at":"2024-11-06T19:46:00.359Z","updated_at":"2025-04-09T06:11:33.625Z","avatar_url":"https://github.com/simov.png","language":"JavaScript","readme":"\n# request-compose\n\n[![npm-version]][npm] [![test-ci-img]][test-ci-url] [![test-cov-img]][test-cov-url] [![snyk-vulnerabilities]][snyk]\n\n\u003e _Composable HTTP Client_\n\n```js\nvar compose = require('request-compose')\nvar Request = compose.Request\nvar Response = compose.Response\n\n;(async () =\u003e {\n  try {\n    var {res, body} = await compose(\n      Request.defaults({headers: {'user-agent': 'request-compose'}}),\n      Request.url('https://api.github.com/users/simov'),\n      Request.send(),\n      Response.buffer(),\n      Response.string(),\n      Response.parse(),\n    )()\n    console.log(res.statusCode, res.statusMessage)\n    console.log(res.headers['x-ratelimit-remaining'])\n    console.log(body)\n  }\n  catch (err) {\n    console.error(err)\n  }\n})()\n```\n\n# Goals\n\n- **No dependencies**\n- **No abstraction**\n- **No state**\n\n\n# Table of Contents\n\n- [**Compose**](#compose)\n- [Bundled **Middlewares**](#bundled-middlewares)\n- [Opinionated **Client**](#opinionated-client)\n  - [client](#client) / [buffer](#buffer) / [stream](#stream) / [options](#options) / [extend](#extend)\n- [**Errors**](#errors)\n- [Debug **Logs**](#debug-logs)\n- [**Examples**](#examples)\n\n\n# Compose\n\n\u003e In computer science, __[function composition][function-composition]__ (not to be confused with object composition) is an act or mechanism to combine simple functions to build more complicated ones. Like the usual composition of functions in mathematics, the result of each function is passed as the argument of the next, and the result of the last one is the result of the whole.\n\n```js\nvar compose = require('request-compose')\n```\n\nAccepts a list of functions to execute and returns a [Promise]:\n\n```js\nvar doit = compose(\n  (a) =\u003e a + 2,\n  (a) =\u003e a * 2,\n)\n```\n\nThen we can call it:\n\n```js\nvar result = await doit(5) // 14\n```\n\nA more practical example however would be to compose our own HTTP client:\n\n```js\nvar compose = require('request-compose')\nvar https = require('https')\n\nvar request = compose(\n  (options) =\u003e {\n    options.headers = options.headers || {}\n    options.headers['user-agent'] = 'request-compose'\n    return options\n  },\n  (options) =\u003e new Promise((resolve, reject) =\u003e {\n    https.request(options)\n      .on('response', resolve)\n      .on('error', reject)\n      .end()\n  }),\n  async (res) =\u003e await new Promise((resolve, reject) =\u003e {\n    var body = ''\n    res\n      .on('data', (chunk) =\u003e body += chunk)\n      .on('end', () =\u003e resolve({res, body}))\n      .on('error', reject)\n  }),\n  ({res, body}) =\u003e ({res, body: JSON.parse(body)}),\n)\n```\n\nThen we can use it like this:\n\n```js\n;(async () =\u003e {\n  try {\n    var {res, body} = await request({\n      protocol: 'https:',\n      hostname: 'api.github.com',\n      path: '/users/simov',\n    })\n    console.log(res.statusCode, res.statusMessage)\n    console.log(res.headers['x-ratelimit-remaining'])\n    console.log(body)\n  }\n  catch (err) {\n    console.error(err)\n  }\n})()\n```\n\n# Bundled Middlewares\n\n`request-compose` comes with a bunch of pre-defined middlewares for transforming the [request][request-middlewares] and the [response][response-middlewares]:\n\n```js\nvar compose = require('request-compose')\nvar Request = compose.Request\nvar Response = compose.Response\n```\n\nWe can use these middlewares to compose our own HTTP client:\n\n```js\n;(async () =\u003e {\n  try {\n    var {res, body} = await compose(\n      Request.defaults({headers: {'user-agent': 'request-compose'}}),\n      Request.url('https://api.github.com/users/simov'),\n      Request.send(),\n      Response.buffer(),\n      Response.string(),\n      Response.parse(),\n    )()\n    console.log(res.statusCode, res.statusMessage)\n    console.log(res.headers['x-ratelimit-remaining'])\n    console.log(body)\n  }\n  catch (err) {\n    console.error(err)\n  }\n})()\n```\n\n| Type | Middleware | Input | Arguments | Returns\n| :--- | :---       | :---  | :---      | :---\n| Request | defaults | {input} | {input} | {options}\n| Request | url, proxy, qs, cookie | see [options](#options) | {options} | {options}\n| Request | form, json, multipart, body | see [options](#options) | {options} | {options, body}\n| Request | auth, oauth | see [options](#options) | {options, body} | {options, body}\n| Request | length | - | {options, body} | {options, body}\n| Request | send | - | {options, body} | {options, res}\n| Response | buffer | - | {options, res} | {options, res, body}\n| Response | gzip | - | {options, res, body, raw} | {options, res, body, raw}\n| Response | string | see [options](#options) | {options, res, body, raw} | {options, res, body, raw}\n| Response | parse, status | - | {options, res, body, raw} | {options, res, body, raw}\n| Response | redirect | (input, client) | {options, res, body, raw} | new composition\n\n\n# Opinionated Client\n\n`request-compose` comes with opinionated HTTP client that is composed of the above [middlewares](#bundled-middlewares).\n\nThere are 3 types of composition available based on the returned data type:\n\n## client\n\n```js\nvar request = require('request-compose').client\nvar {res, body} = await request({options})\n```\n\nThe `client` composition does the following:\n\n- buffers the response body\n- decompresses `gzip` and `deflate` encoded bodies with valid `content-encoding` header\n- converts the response body to string using `utf8` encoding by default\n- tries to parse `JSON` and `querystring` encoded bodies with valid `content-type` header\n\nReturns either String or Object.\n\n## buffer\n\n```js\nvar request = require('request-compose').buffer\nvar {res, body} = await request({options})\n```\n\nThe `buffer` composition does the following:\n\n- buffers the response body\n- decompresses `gzip` and `deflate` encoded bodies with valid `content-encoding` header\n\nReturns [Buffer][buffer].\n\n## stream\n\n```js\nvar request = require('request-compose').stream\nvar {res} = await request({options})\n```\n\nThe `stream` composition returns the response [Stream][stream-incoming-message].\n\n## options\n\nThe above compositions accept any of the Node's [http.request][http-request] and [https.request][https-request] options:\n\n```js\nvar {res, body} = await request({\n  method: 'GET',\n  url: 'https://api.github.com/users/simov',\n  headers: {\n    'user-agent': 'request-compose'\n  }\n})\n```\n\nAdditionally the following options are available:\n\n| Option     | Type                  | Description\n| :--        | :--                   | :--\n| `url`      | `'string'` [`url object`][url-parse] | URL _(encoding - see below)_\n| `proxy`    | `'string'` [`url object`][url-parse] | Proxy URL\n| `qs`       | `{object}` `'string'` | URL querystring _(encoding - see below)_\n| `form`     | `{object}` `'string'` | application/x-www-form-urlencoded request body _(encoding - see below)_\n| `json`     | `{object}` `'string'` | JSON encoded request body\n| `multipart`| `{object}` `[array]`  | multipart request body using [request-multipart], see [examples](#external-middlewares)\n| `body`     | `'string'` [`Buffer`][buffer] [`Stream`][stream-readable] | request body\n| `auth`     | `{user, pass}`        | Basic authorization\n| `oauth`    | `{object}` | OAuth 1.0a authorization using [request-oauth], see [examples](#external-middlewares)\n| `encoding` | [`'string'`][buffer-encoding] | response body encoding _(default: 'utf8')_\n| `cookie`   | `{object}` | cookie store using [request-cookie], see [examples](#external-middlewares)\n| `redirect` | `{object}` | _see below_\n\n\u003e Querystring set in the `url`, and/or in `qs` and/or in `form` as _'string'_ is left untouched, meaning that the proper encoding is left to the user.\n\n\u003e When `qs` and/or `form` is _{object}_ the querystring is encoded using the Node's [querystring] module which mirrors the global [encodeURIComponent][encodeuri] method. Additionally all reserved characters according to RFC3986 are encoded as well. Full list of all reserved characters that are being encoded can be found [here][reserved-characters].\n\n#### redirect\n\n| Option    | Default | Description\n| :--       | :--     | :--\n| `max`     | *3*     | maximum number of redirects to follow\n| `all`     | *false* | follow non-GET HTTP 3xx responses as redirects\n| `method`  | *true*  | follow original HTTP method, otherwise convert all redirects to GET\n| `auth`    | *true*  | keep Authorization header when changing hostnames\n| `referer` | *false* | add Referer header\n\n## extend\n\nExtend or override any of the bundled [request][request-middlewares] and [response][response-middlewares] middlewares:\n\n```js\nvar request = require('request-compose').extend({\n  Request: {\n    oauth: require('request-oauth'),\n    multipart: require('request-multipart'),\n    cookie: require('request-cookie').Request\n  },\n  Response: {cookie: require('request-cookie').Response},\n}).client\n```\n\n# Errors\n\nNon `200/300` responses are thrown as [Error] object with the following properties:\n\n- `message` - status code + status message\n- `res` - the response object\n- `body` - the parsed response body\n- `raw` - the raw response body\n\n\n# Debug Logs\n\nFancy [request-logs]:\n\n```bash\nnpm i --save-dev request-logs\n```\n\nPick any of the following debug options:\n\n```bash\nDEBUG=req,res,body,json,nocolor node app.js\n```\n\n# Examples\n\n| Topic | Example\n| :--   | :--\n| **`Basics`** |\n| Types of lambda functions | [Get GitHub user profile](https://github.com/simov/request-compose/blob/master/examples/basic-lambda.js)\n| Bundled middlewares | [Get GitHub user profile](https://github.com/simov/request-compose/blob/master/examples/basic-middlewares.js)\n| Wrap it up and extend it | [Get GitHub user profile](https://github.com/simov/request-compose/blob/master/examples/basic-extend.js)\n| **`Compositions`** |\n| Client | [Get GitHub user profile](https://github.com/simov/request-compose/blob/master/examples/compose-client.js)\n| Buffer | [Decoding response body using iconv-lite](https://github.com/simov/request-compose/blob/master/examples/compose-buffer.js)\n| Stream | [Stream Tweets](https://github.com/simov/request-compose/blob/master/examples/compose-stream.js)\n| **`External Middlewares`** |\n| OAuth ([request-oauth]) | [Get Twitter User Profile](https://github.com/simov/request-compose/blob/master/examples/mw-oauth.js)\n| Multipart ([request-multipart]) | [Upload photo to Twitter](https://github.com/simov/request-compose/blob/master/examples/mw-multipart.js)\n| Cookie ([request-cookie]) | [Login to Wallhaven.cc](https://github.com/simov/request-compose/blob/master/examples/mw-cookie.js)\n| **`Stream`** |\n| Stream request body | [Upload file to Dropbox](https://github.com/simov/request-compose/blob/master/examples/stream-dropbox-upload.js)\n| HTTP stream | [Upload image from Dropbox to Slack](https://github.com/simov/request-compose/blob/master/examples/stream-dropbox-to-slack.js)\n| HTTP stream | [Copy file from Dropbox to GDrive](https://github.com/simov/request-compose/blob/master/examples/stream-dropbox-to-gdrive.js)\n| **`Misc`** |\n| Gzip decompression | [Request Gzip compressed body](https://github.com/simov/request-compose/blob/master/examples/misc-gzip.js)\n| HTTPS proxy | [Tunnel Agent](https://github.com/simov/request-compose/blob/master/examples/misc-tunnel-agent.js)\n| HTTPS proxy | [Proxy Agent](https://github.com/simov/request-compose/blob/master/examples/misc-proxy-agent.js)\n| Override bundled middleware - per compose instance | [Override the `qs` middleware](https://github.com/simov/request-compose/blob/master/examples/misc-extend.js)\n| Override bundled middleware - process-wide | [Override the `form` and the `parse` middlewares to use the `qs` module](https://github.com/simov/request-compose/blob/master/examples/misc-override.js)\n| **`Pipeline`** |\n| App pipeline | [Slack Weather Status](https://github.com/simov/request-compose/blob/master/examples/pipe-slack-weather-status.js)\n| App pipeline | [Simultaneously search for repos in GitHub, GitLab and BitBucket](https://github.com/simov/request-compose/blob/master/examples/pipe-repo-search.js)\n| **`Modules`** |\n| Google Chrome Web Store HTTP Client | [chrome-webstore]\n| REST API Client Library | [purest]\n\n\n  [npm-version]: https://img.shields.io/npm/v/request-compose.svg?style=flat-square (NPM Version)\n  [test-ci-img]: https://img.shields.io/github/actions/workflow/status/simov/request-compose/test.yml?style=flat-square (Build Status)\n  [test-cov-img]: https://img.shields.io/coveralls/simov/request-compose.svg?style=flat-square (Test Coverage)\n  [snyk-vulnerabilities]: https://img.shields.io/badge/vulnerabilities-0-geen?style=flat-square (Vulnerabilities)\n\n  [npm]: https://www.npmjs.com/package/request-compose\n  [test-ci-url]: https://github.com/simov/request-compose/actions/workflows/test.yml\n  [test-cov-url]: https://coveralls.io/r/simov/request-compose?branch=master\n  [snyk]: https://snyk.io/test/npm/request-compose\n\n  [function-composition]: https://en.wikipedia.org/wiki/Function_composition_(computer_science)\n  [pipeline]: https://en.wikipedia.org/wiki/Pipeline_(software)\n  [pipe-operator]: https://github.com/tc39/proposal-pipeline-operator\n\n  [chunked]: https://en.wikipedia.org/wiki/Chunked_transfer_encoding\n\n  [request-middlewares]: https://github.com/simov/request-compose/tree/master/request\n  [response-middlewares]: https://github.com/simov/request-compose/tree/master/response\n  [request-oauth]: https://www.npmjs.com/package/request-oauth\n  [request-multipart]: https://www.npmjs.com/package/request-multipart\n  [request-cookie]: https://www.npmjs.com/package/request-cookie\n  [request-logs]: https://www.npmjs.com/package/request-logs\n\n  [promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise\n  [error]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error\n\n  [buffer]: https://nodejs.org/dist/latest-v10.x/docs/api/buffer.html\n  [buffer-encoding]: https://nodejs.org/dist/latest-v10.x/docs/api/buffer.html#buffer_buffers_and_character_encodings\n  [stream-readable]: https://nodejs.org/dist/latest-v10.x/docs/api/stream.html#stream_class_stream_readable\n  [stream-incoming-message]: https://nodejs.org/dist/latest-v10.x/docs/api/http.html#http_class_http_incomingmessage\n  [http-request]: https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_http_request_options_callback\n  [https-request]: https://nodejs.org/dist/latest-v14.x/docs/api/https.html#https_https_request_options_callback\n  [url-parse]: https://nodejs.org/dist/latest-v10.x/docs/api/url.html#url_url_parse_urlstring_parsequerystring_slashesdenotehost\n  [querystring-parse]: https://nodejs.org/dist/latest-v10.x/docs/api/querystring.html#querystring_querystring_parse_str_sep_eq_options\n  [querystring]: https://nodejs.org/dist/latest-v10.x/docs/api/querystring.html\n  [encodeuri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent\n  [reserved-characters]: https://en.wikipedia.org/wiki/Percent-encoding#Types_of_URI_characters\n\n  [chrome-webstore]: https://github.com/simov/chrome-webstore\n  [purest]: https://github.com/simov/purest\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimov%2Frequest-compose","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimov%2Frequest-compose","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimov%2Frequest-compose/lists"}