This series of posts catalogs basic functions in Node via the built-in APIs. There are many libraries that provide syntactic sugar over these functions. However, it's a good exercise to understand how to do them manual. As serverless architectures increase in popularity, for performance reasons it's important to reduce dependencies and size of the deployment packages.

I'm starting this series with a post on making HTTP GET requests since it was one of the recent changes I made to a serverless application to improve performance. This functionality is found in https://nodejs.org/api/http.html

GET Request

The simplest request to make is a GET request. This example converts the request into a Promise based function using the standard Promise class.

function get({ host, path }) {
  return new Promise((resolve, reject) => {

    // construct the http options
    let opts = {
      host,
      path,
    };

    // create a callback function to handle the response
    function callback(res) {
      let buffers = [];
      res.on('data', (buffer) => buffers.push(buffer));
      res.on('error', reject);
      res.on('end', () => res.statusCode === 200 
          ? resolve(Buffer.concat(buffers))
          : reject(Buffer.concat(buffers)));
    }

    // initiate the http request
    let req = http.request(opts, callback);
    req.on('error', reject);
    req.end();

  });
}

Breaking this down... The first section creates the request options that are defined in the http request API. In this instance, I'm only defining the host and path properties.

// construct the http options
let opts = {
  host,
  path,
};

The next section defines the callback. The callback from a request has a single argument, the response. The callback function will perform three functions using this response.

  • listen to the data event and collect all buffers that are reported
  • listen to the error event and immediately reject if there is an error
  • listen to the end event and depending on the status code will either resolve or reject the response by combining all of the buffers that were compiled during the data events.
// create a callback function to handle the response
function callback(res) {
  let buffers = [];
  res.on('data', (buffer) => buffers.push(buffer));
  res.on('error', reject);
  res.on('end', () => res.statusCode === 200 
      ? resolve(Buffer.concat(buffers))
      : reject(Buffer.concat(buffers)));
}

This pattern is suitable for small requests (such as web page fetches) due to how it keeps all buffer parts in memory. This pattern would not be suitable for large request where you would want to return the result object directly and stream it to disk or some other output location.

The final piece is actually initiating the request. The request function takes two arguments options and callback and returns an instance of ClientRequest.

With this instance, it will listen for an error event and reject the promise if encountered. Otherwise it will finalize the request by calling end.

// initiate the http request
let req = http.request(opts, callback);
req.on('error', reject);
req.end();

Finally, to use this new helper function, you can simply call it as you would any other promise:

get({ host: 'www.southsidecomicspgh.com', path: '/' })
  .then(buffer  => console.log(buffer.toString()))
  .catch(buffer => console.error(buffer.toString()));

The next article will discuss making POST requests with the HTTP APIs.