Home

Awesome

request-compose

npm-version test-ci-img test-cov-img snyk-vulnerabilities

Composable HTTP Client

var compose = require('request-compose')
var Request = compose.Request
var Response = compose.Response

;(async () => {
  try {
    var {res, body} = await compose(
      Request.defaults({headers: {'user-agent': 'request-compose'}}),
      Request.url('https://api.github.com/users/simov'),
      Request.send(),
      Response.buffer(),
      Response.string(),
      Response.parse(),
    )()
    console.log(res.statusCode, res.statusMessage)
    console.log(res.headers['x-ratelimit-remaining'])
    console.log(body)
  }
  catch (err) {
    console.error(err)
  }
})()

Goals

Table of Contents

Compose

In computer science, 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.

var compose = require('request-compose')

Accepts a list of functions to execute and returns a Promise:

var doit = compose(
  (a) => a + 2,
  (a) => a * 2,
)

Then we can call it:

var result = await doit(5) // 14

A more practical example however would be to compose our own HTTP client:

var compose = require('request-compose')
var https = require('https')

var request = compose(
  (options) => {
    options.headers = options.headers || {}
    options.headers['user-agent'] = 'request-compose'
    return options
  },
  (options) => new Promise((resolve, reject) => {
    https.request(options)
      .on('response', resolve)
      .on('error', reject)
      .end()
  }),
  async (res) => await new Promise((resolve, reject) => {
    var body = ''
    res
      .on('data', (chunk) => body += chunk)
      .on('end', () => resolve({res, body}))
      .on('error', reject)
  }),
  ({res, body}) => ({res, body: JSON.parse(body)}),
)

Then we can use it like this:

;(async () => {
  try {
    var {res, body} = await request({
      protocol: 'https:',
      hostname: 'api.github.com',
      path: '/users/simov',
    })
    console.log(res.statusCode, res.statusMessage)
    console.log(res.headers['x-ratelimit-remaining'])
    console.log(body)
  }
  catch (err) {
    console.error(err)
  }
})()

Bundled Middlewares

request-compose comes with a bunch of pre-defined middlewares for transforming the request and the response:

var compose = require('request-compose')
var Request = compose.Request
var Response = compose.Response

We can use these middlewares to compose our own HTTP client:

;(async () => {
  try {
    var {res, body} = await compose(
      Request.defaults({headers: {'user-agent': 'request-compose'}}),
      Request.url('https://api.github.com/users/simov'),
      Request.send(),
      Response.buffer(),
      Response.string(),
      Response.parse(),
    )()
    console.log(res.statusCode, res.statusMessage)
    console.log(res.headers['x-ratelimit-remaining'])
    console.log(body)
  }
  catch (err) {
    console.error(err)
  }
})()
TypeMiddlewareInputArgumentsReturns
Requestdefaults{input}{input}{options}
Requesturl, proxy, qs, cookiesee options{options}{options}
Requestform, json, multipart, bodysee options{options}{options, body}
Requestauth, oauthsee options{options, body}{options, body}
Requestlength-{options, body}{options, body}
Requestsend-{options, body}{options, res}
Responsebuffer-{options, res}{options, res, body}
Responsegzip-{options, res, body, raw}{options, res, body, raw}
Responsestringsee options{options, res, body, raw}{options, res, body, raw}
Responseparse, status-{options, res, body, raw}{options, res, body, raw}
Responseredirect(input, client){options, res, body, raw}new composition

Opinionated Client

request-compose comes with opinionated HTTP client that is composed of the above middlewares.

There are 3 types of composition available based on the returned data type:

client

var request = require('request-compose').client
var {res, body} = await request({options})

The client composition does the following:

Returns either String or Object.

buffer

var request = require('request-compose').buffer
var {res, body} = await request({options})

The buffer composition does the following:

Returns Buffer.

stream

var request = require('request-compose').stream
var {res} = await request({options})

The stream composition returns the response Stream.

options

The above compositions accept any of the Node's http.request and https.request options:

var {res, body} = await request({
  method: 'GET',
  url: 'https://api.github.com/users/simov',
  headers: {
    'user-agent': 'request-compose'
  }
})

Additionally the following options are available:

OptionTypeDescription
url'string' url objectURL (encoding - see below)
proxy'string' url objectProxy URL
qs{object} 'string'URL querystring (encoding - see below)
form{object} 'string'application/x-www-form-urlencoded request body (encoding - see below)
json{object} 'string'JSON encoded request body
multipart{object} [array]multipart request body using request-multipart, see examples
body'string' Buffer Streamrequest body
auth{user, pass}Basic authorization
oauth{object}OAuth 1.0a authorization using request-oauth, see examples
encoding'string'response body encoding (default: 'utf8')
cookie{object}cookie store using request-cookie, see examples
redirect{object}see below

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.

When qs and/or form is {object} the querystring is encoded using the Node's querystring module which mirrors the global encodeURIComponent 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.

redirect

OptionDefaultDescription
max3maximum number of redirects to follow
allfalsefollow non-GET HTTP 3xx responses as redirects
methodtruefollow original HTTP method, otherwise convert all redirects to GET
authtruekeep Authorization header when changing hostnames
refererfalseadd Referer header

extend

Extend or override any of the bundled request and response middlewares:

var request = require('request-compose').extend({
  Request: {
    oauth: require('request-oauth'),
    multipart: require('request-multipart'),
    cookie: require('request-cookie').Request
  },
  Response: {cookie: require('request-cookie').Response},
}).client

Errors

Non 200/300 responses are thrown as Error object with the following properties:

Debug Logs

Fancy request-logs:

npm i --save-dev request-logs

Pick any of the following debug options:

DEBUG=req,res,body,json,nocolor node app.js

Examples

TopicExample
Basics
Types of lambda functionsGet GitHub user profile
Bundled middlewaresGet GitHub user profile
Wrap it up and extend itGet GitHub user profile
Compositions
ClientGet GitHub user profile
BufferDecoding response body using iconv-lite
StreamStream Tweets
External Middlewares
OAuth (request-oauth)Get Twitter User Profile
Multipart (request-multipart)Upload photo to Twitter
Cookie (request-cookie)Login to Wallhaven.cc
Stream
Stream request bodyUpload file to Dropbox
HTTP streamUpload image from Dropbox to Slack
HTTP streamCopy file from Dropbox to GDrive
Misc
Gzip decompressionRequest Gzip compressed body
HTTPS proxyTunnel Agent
HTTPS proxyProxy Agent
Override bundled middleware - per compose instanceOverride the qs middleware
Override bundled middleware - process-wideOverride the form and the parse middlewares to use the qs module
Pipeline
App pipelineSlack Weather Status
App pipelineSimultaneously search for repos in GitHub, GitLab and BitBucket
Modules
Google Chrome Web Store HTTP Clientchrome-webstore
REST API Client Librarypurest