Builder pattern in Node.js

In this post, I'm going to explain the builder pattern in Node.js. I'll use the code constructed in my previous posts - see the repo. Because the changes are not compatible with the previous version, they are committed to builder-pattern branch

Builder is useful whenever you need to build an object in steps, and those steps should be abstracted away from the client code. My most favorite example is XML builder. Usually, when it comes to building XML documents we don't want to use the very raw strings. People defined builders for such tasks:

var builder = require('xmlbuilder');
var xml = builder.create('root')
  .ele('rst')
    .ele('bartek')
...

This solution has multiple advantages:

  • no need to remember the exact format of XML;
  • fewer characters (= less typing);
  • you can leverage the IDE, for example to typeahead the methods or to verify the syntax;
  • the builders can help you in constructing the document - for instance by throwing exception on attempt to build an invalid document;
  • perhaps the most important one - the code is cleaner. The logic is separated from the details.

Our case is slightly different. We are going to make multiple API calls, but expect the result to be a single data object. In particular we will call those three endpoints:

/<pageId>
/<pageId>/page_fans_country
/<pageId>/page_storytellers_by_country

...and, for the last two, process each into a single value for page object.

We'll use two service objects. The one for the first endpoint is already done (needs some modification, though). The second one will cover the remaining two,

Here is how to adjust FetchPage service:

// fetchPage.js
require('rootpath')();
var facebookAdapter = require('adapters/facebook/facebookAdapter');

module.exports = function FetchPage() {
  var that = this;
  that.resolve = null;
  that.reject = null;
  that.response = null;

  this.run = function (pageId) {
    // (...)
  };

  function fetchPageInfo(pageId) {
    // (...)
  }

  function onSuccess(rawResponse) {
    that.response = rawResponse;
    validateResponse();

    // return raw response - let the builder parse it.
    that.resolve(rawResponse);
  }

  function onFail(error) {
    // (...)
  }

  function validateResponse() {
    // (...)
  }
  
  // function to parse the reposonse moved to builder as well.
};

The second service object - let's dub it FetchPageInsights - is essentially the same but connecting to other endpoints (remember, this service covers two endpoints, unlike FetchPage).

Here's how they differ:

// FetchPageInsights.js
(...)

module.exports = function FetchPageInsights() {
(...)

  // note additional argument
  this.run = function (pageId, metric) {
    return new Promise(
      function (resolve, reject) {
        that.resolve = resolve;
        that.reject = reject;
        fetchPageInsights(pageId, metric).then(onSuccess, onFail);
      }
    );
  };

  // different endpoint
  function fetchPageInsights(pageId, metric) {
    var pathname = pageId + '/insights/' + metric;
    var options = {};
    return facebookAdapter.fetch(pathname, options);
  }

  // the same callbacks
  function onSuccess(rawResponse) (...)
  function onFail(error) (...)

  // different validation
  function validateResponse() {
    if (!that.response.data)
      that.reject('Non-page object provided');
  }
};

And that’s where the problem starts: now we have to build a single object from three asynchronous calls? You guessed it - PageBuilder:

module.exports = function PageBuilder(id) {
  var campaign = {
    id: id,
  };

  this.getResult = function () {
    return campaign;
  };

  this.fillPageData = function (rawResponse) {
    campaign.name = rawResponse.name;
    campaign.city = rawResponse.location.city;
    campaign.video_posts = rawResponse.posts.data.filter(isVideoType);
  };

  this.fillFans = function (rawResponse) {
    var fansPerCountry = rawResponse.data[0].values[0].value;
    campaign.mostFansFrom = findKeyOfMaxValue(fansPerCountry);
  };

  this.fillStorytellers = function (rawResponse) {
    var storyellersPerCountry = rawResponse.data[0].values[0].value;
    campaign.mostStorytellersFrom = findKeyOfMaxValue(storyellersPerCountry);
  };

  function isVideoType(post) {
    return (post.type === 'video');
  }

  function findKeyOfMaxValue(object) {
    var max = 0;
    var keyForMaxValue = null;
    for (var property in object) {
      if (object.hasOwnProperty(property)) {
        if (object[property] > max) {
          max = object[property];
          keyForMaxValue = property;
        }
      }
    }

    return keyForMaxValue;
  }
};

A few explanations:

  • PageBuilder takes pageId in constructor to pre populate the object from it. pageId is used also by every service object, therefore any of them could have been used to populate the ID of the built page object. However, I couldn't decide which request would fit best, plus pageId has to be known upfront, so I went for this solution.

  • The builder exposes three methods - fillPageData, fillFans and fillStorytellers - to build a part of the object, each based on a different raw response (which primarily come from the service objects, but again - not necessarily - could come from tests, for example).

  • The last method - getResult - is supposed to be used after all three other methods are done.

  • How to get it done in an async environment - we'll use Promises again, and write yet another service object to do that. This one will be a kind of "glue" between the other service objects and the builder:

// getPageObject.js
module.exports = function GetPageObject() {
  var that = this;

  this.run = function (pageId) {
    var builder = new PageBuilder(pageId);

    var fetchingPromises = [
      new FetchPage().run(pageId)
        .then(builder.fillPageData),
      new FetchPageInsights().run(pageId, 'page_fans_country')
        .then(builder.fillFans),
      new FetchPageInsights().run(pageId, 'page_storytellers_by_country')
        .then(builder.fillStorytellers),
    ];

    return new Promise(
      function (resolve, reject) {
        Promise.all(fetchingPromises).then(
          function () { resolve(builder.getResult()); },

          function (error) { reject(error); }
        );
      }
    );
  };
};

Beautiful, isn't it? I think that having your code organized so well that you can write lines like: new FetchPage().run(paged).then(builder.fillPageData) is a reward in itself.

In case you want to verify if it works:

require('rootpath')();
var GetPageObject = require('services/getPageObject');

new GetPageObject().run('rstit').then(console.log);

Bottom line

This seems to be a common problem in Node.js - to build an object from multiple asynchronous, therefore unpredictable, requests. At the end of the day, I believe using the builder pattern (backed by the service objects and the adapter) is a very neat solution.

Feel free to comment below, I would love to hear from you!