Useful Tips for Building JavaScript APIs

In this tutorial we're going to cover some tips on building JavaScript APIs. We will be using chaining as an object oriented approach, and composition as a functional approach.

By the end, we will build a helper that will let us bridge both worlds, and have an API that caters for every use.

Build JavaScript APIs

Fluent Interfaces

Fluent interfaces can be implemented in JavaScript using an object oriented approach. You have a constructor function whose prototype contains methods that always return the instance, so we can create a chain of operations, thus "fluent". In other words:

function Constructor(){}
Constructor.protototype.method = function(x) {
  // do something
  return this
}
Constructor.prototype.otherMethod = function(x) {
  // do something else
  return this
}
// Usage:
var instance = new Constructor
instance.method().otherMethod()

We can already see a pattern here: return this. We can abstract it one more level, by creating a function decorator that does the job for us, plus it tells us about our fluent interface up-front, in a more declarative way. A function decorator is a higher-order function; a function that takes a function as argument, and returns a modified version of that function, in this case we simply want to add the ability to always return this, that's our "decoration":

var fluent = function(f) {
  return function() {  
    f.apply(this, arguments)
    return this
  }
}

Now we can be a bit more expressive in our intent:

Constructor.prototype.method = fluent(function(x) {
  // do something and `return this`
})

To improve upon our previous code, and to avoid leaking variables to the global scope, the last step is wrapping everything in an immediately invoked function expression (IIFE):

var Constructor = (function(){
  function Constructor(){}
  Constructor.prototype.method = function(){}
  ...
  return Constructor
}())

Good, now we have a good starting point. Let's use it in practice. Let's create a simple calculator. First, we setup the IIFE and the constructor:

var Calc = (function(){
  function Calc(num) {
    this.num = num
  }
  // methods here
  return Calc
}())

Next, we need some operations. Let's keep it simple with just 4 operations: add, subtract, multiply and divide:

Calc.prototype.add = fluent(function(x){this.num += x})
Calc.prototype.sub = fluent(function(x){this.num -= x})
Calc.prototype.mul = fluent(function(x){this.num *= x})
Calc.prototype.div = fluent(function(x){this.num /= x})

Now we have a fluent calculator interface. We create a new instance passing a number, and then we call the methods in a chain:

var calc = new Calc(2)

calc.add(1).mul(2).num //=> 6

That was easy! This pattern is very well known; libraries such as jQuery, D3, and others, have used it successfully. Next, we'll see an alternative functional approach to the same problem.

Composition

In functional programming data must be upfront, that means that we shouldn't encapsulate things behind an object, but make things as public and general as possible. We need to accommodate our functions to work on data structures, not model objects that interact in a closed state box.

Let's create the same calculator example, but this time we're going to build a functional API. Using composition is similar to chaining. With a bit of wishful thinking we can see how they compare to each other:

// chaining
new Calc(2).add(1).mul(2)

// composition
compose(mul(2), add(1))(2)

As you can see composition is expressed from right to left, this is because that's how functions naturally compose. But the good news is that we can also express it the other way around if we want to. Given a compose helper (you can use Underscore for example), we can flip its arguments around. Flipping is a very common thing to do in functional programming, because sometimes the arguments aren't in the proper order and the function fails to compose. Let's create a flip helper, that will work for variadic functions (functions that take any number of arguments):

var flipN = function(f) {
  return function() {
    return f.apply(this, [].slice.call(arguments).reverse())
  }
}

Above, we create a real array from the arguments object, and we simply reverse it, and apply the function. Now we can create the reverse of compose, which we'll call sequence:

// composition
compose(mul(2), add(1))(2)

// sequencing
var sequence = flipN(compose)
sequence(add(1), mul(2))(2)

Both will yield the same results, but the last one looks more natural to read; it is closer to a fluent API meant for humans.

Now, what are these add(1) and mul(2)function calls? The answer is a bit more complex in theory than in practice, so let's start with the hard part. In functional programming we strive for high abstractions, and for things that compose well. But, composition only works with functions of one argument, because a function can only return one thing. So how do we get around this issue? The solution is currying. A function that takes 2 or more arguments can be transformed into a function that takes one single argument at a time, and returns a function with the next argument to come, until all arguments have been taken, at which point it will return the final value.

We can build a simple example using the add function:

// no currying
function add(x, y) {
  return x + y
}

add(1, 2) //=> 3

// currying
function add(x) {
  return function(y) {
    return x + y
  }
}

add(1)(2) //=> 3

As you can see, calling the function now is a bit of a hassle, because it only accepts one argument at a time. This is not a big deal in practice, and fortunately, since JavaScript has variadic arguments, we can build a generic curry helper that will give us back a curried version of a function that we can call with many arguments, or with one argument at a time. We won't implement curry for the purpose of this tutorial, but you can use LoDash or my own implementation:

var add = curry(function add(x, y){return x + y})

// works both ways
add(1, 2) //=> 3
add(1)(2) //=> 3

Now let's stop and think for a minute. Composition is highly dependent on the order of arguments, as you already know. The receiver of the operation in the OO approach always comes first, for example:

instance.add(1) // `instance` is the receiver

But with composition, as a general rule, we want to have the receiver (which is just one more argument) at the end:

var add = curry(function add(x, y){return y + x})

Of course, with addition (and multiplication), the order does not matter, because these are not only associative, but also commutative operations.

With this in mind we can complete our calculator API:

var add = curry(function add(x, y){return y + x})
var sub = curry(function add(x, y){return y - x})
var mul = curry(function add(x, y){return y * x})
var div = curry(function add(x, y){return y / x})

These functions that we have created so far live in the global scope, while the OO approach was encapsulated. To group all our functions under a single namespace, we can again make use of an IIFE:

var calc = (function(){
  // code here
  return {
    add: add,
    sub: sub,
    mul: mul,
    div: div
  }
}())

Now we'd use it like:

sequence(calc.add(1), calc.mul(2))(2) //=> 6

You could also import the functions into the global scope with an extends helper, such as the one provided by jQuery:

extend(window, calc)

Mixing both worlds

We've seen how to use chaining and composition to build an API, but what if we can have the best of both worlds? We want the higher abstractions, and the re-usability that the FP approach gives us, but we also may want to use chaining if we're working with other OO libraries, to maintain consistency. This is quite simple actually. Since our FP approach puts the receiver at the end, we can make use of variadic functions to slice that argument out, and make it be the receiver of a fluent interface:

var Chainable = function (ctor, methods, val) {
  val = val || 'val'
  Object.keys(methods).forEach(function(k) {
    ctor.prototype[k] = fluent(function() {
      var as = [].slice.call(arguments)
      this[val] = methods[k].apply(this, as.concat([this[val]]))
    })
  })
  return ctor
}

With this helper we can build our APIs using composition, and then, when everything is working, we simply generate the fluent API dynamically:

var MyCalc = Chainable(
  function Calc(num) {
    this.num = num
  },
  { add: calc.add,
    sub: calc.sub,
    mul: calc.mul,
    div: calc.div
  },
  'num'
)

Now you can use it just like the version we created in the OO approach:

new MyCalc(2).add(1).mul(2).num //=> 6

Final Code

// Helpers
var fluent = function(f) {
  return function() {  
    f.apply(this, arguments)
    return this
  }
}
var flipN = function(f) {
  return function() {
    return f.apply(this, [].slice.call(arguments).reverse())
  }
}
var curryN = function(n, f, as) {
  as = as || []
  return function() {
    var bs = as.concat([].slice.call(arguments))
    if (bs.length < n) {
      return curryN(n, f, bs)
    }
    return f.apply(null, bs)
  }
}
var curry = function(f) {
  return function() {
    var as = [].slice.call(arguments)
    if (f.length > as.length) {
      return curryN(f.length, f, as)
    }
    return f.apply(null, as)
  }
}
var compose = function() {
  return [].reduce.call(arguments, function(f, g) {
    return function() {
      return f(g.apply(this, arguments))
    }
  })
}
var sequence = flipN(compose)

// OO - Fluent Interface
var Calc = (function(){
  function Calc(num) {
    this.num = num
  }
  Calc.prototype.add = fluent(function(x){this.num += x})
  Calc.prototype.sub = fluent(function(x){this.num -= x})
  Calc.prototype.mul = fluent(function(x){this.num *= x})
  Calc.prototype.div = fluent(function(x){this.num /= x})
  return Calc
}())

// FP - Composition
var calc = (function(){
  var add = curry(function add(x, y){return y + x})
  var sub = curry(function add(x, y){return y - x})
  var mul = curry(function add(x, y){return y * x})
  var div = curry(function add(x, y){return y / x})
  return {
    add: add,
    sub: sub,
    mul: mul,
    div: div
  }
}())

// FP->OO Bridge
var Chainable = function (ctor, methods, val) {
  val = val || 'val'
  Object.keys(methods).forEach(function(k) {
    ctor.prototype[k] = fluent(function() {
      var as = [].slice.call(arguments)
      this[val] = methods[k].apply(this, as.concat([this[val]]))
    })
  })
  return ctor
}

// Example
console.log(new Calc(2).add(1).mul(2).num) //=> 6

console.log(sequence(calc.add(1), calc.mul(2))(2)) //=> 6

// Build a chainable interface
var MyCalc = Chainable(
  function Calc(num) {
    this.num = num
  },
  { add: calc.add,
    sub: calc.sub,
    mul: calc.mul,
    div: calc.div
  },
  'num'
)

console.log(new MyCalc(2).add(1).mul(2).num) //=> 6

Conclusion

Composition and fluent interfaces are two ways we can build APIs in JavaScript. The ability to go from composition to fluent interface easily gives composition an advantage over chaining, as doing the opposite isn't quite as simple, albeit possible.