The awful inconsistency of Firebase API

Please pick on API pattern and stick to it.

Last night I started adding a new feature to my project was-tested. Instead of accumulating code coverage blindly, I wanted to start collecting this information when the user (typically a QA tester) started going through a test script. Then when the script is finished, the QA can click "stop" and we save the code coverage for a particular feature. This information can be the basis for a lot of interesting projects.

So I wanted a simple way to store some basic information: the name of the feature test, test steps, and maybe code coverage JSON object. I needed to store this data somewhere and as a prototype I have decided to use Firebase backend data service. The main three features that attracted me to Firebase were:

  • simple JSON data store and retrieval
  • Node and AngularJS clients
  • Rich user authentication options

So I have created a project on Firebase and started with a simple Node client to server as a test bed tool. For example I wanted to create a new test, add a second test and fetch all created tests from Node before writing any browser code. Here is my experience with Firebase API.

Using Firebase

Setting up a project is very simple. Once a project ID is issued, I can use the firebase Node package and push first item of data

1
2
3
4
5
6
7
8
9
10
11
12
13
var Firebase = require('Firebase');
var ref = new Firebase('https://<my id>.firebaseio.com/');
var info = {
name: 'first item'
};
ref.set(info, function (err) {
if (err) {
console.error(err);
} else {
console.log('set first test, exitting');
}
process.exit();
});

Notice that the in order to check if the set() method failed, we need to pass a callback that receives an error or nothing. If everything goes fine, can I update the newly created item? No, I do not get the reference to the new value, only the fact that it was created, because the callback fired and there was no error.

I love promises, so I converted the ref.set to promise-based approach

1
2
3
4
var Q = require('q');
var setTest = Q.nbind(ref.set, ref);
setTest(info)
.then(...);

So this worked for a single object. I my case I had a list of test suites to save / update. Thus I needed to keep adding a new test to the list and use ref.push() method. This method is different from the ref.set() - it takes callback, but also returns a value that references the newly created item!

1
2
3
4
var newItem = ref.push(info, function (err) {
...
});
console.log('new item key', newItem.key());

This is extremely weird, using return value AND callback at the same time. But I could convert to a promise-returning method when using ref.set() and I could do so now too

1
2
3
4
5
6
var testPush = Q.nbind(ref.push, ref);
testPush(info)
.then(...)
.catch(function (err) {
console.log('could not add new item');
});

Finally, I needed a way to retrieve all items from a reference. There is a method called ref.once('value') that fetches all data. Here is its signature

1
2
3
ref.once('value', function (dataObject) {
console.log(dataObject.val());
});

There is no return value, but the callback does not follow Node convention - it has data at the first position, not the error. In fact, I do not know if there is a way to handle errors when calling ref.once.

Of course I could write my own adaptor for ref.once method to return a promise. But I cannot say why do some Firebase methods:

  • take Node-style callback argument, while others
  • return a value,
  • yet other methods take a callback with just a data argument?

This is an awful API format for such a small number of methods (Firebase has very few methods). I am going back to the MongoDB data services.