Firebase – A Real Time Document Database

There are a plethora of document databases to choose from nowadays. The entire nature of storing data is changing, so how we work with data needs to change as well. Single page applications on the web need to be responsive, not just in layout but in communication as well. Users have come to expect a higher quality of data representation, and the landscape is quickly evolving.

On the surface, Firebase is like any other document database out there. It stores data as JSON documents, and the idea of relational data is gone. RavenDB, MongoDB, CouchDB all store data primarily the same way. For the most part, Firebase does not differ. What sets Firebase apart, however, is how you communicate with that data. They’ve introduced the concept of “three-way data binding”. In most client side MV* applications, you have your usual two-way data binding: between the model and the view and between the view and the model. But Firebase adds a third layer: between client and server.

What this means is that when data changes on the server, that change is pushed to any client “listening” to that data. And when data changes on the client side, it is pushed back up to the database. In the past, you needed to implement some third party library to achieve this, such as socket.io or SignalR. It added an extra step to your development, you had to wire up all that communication yourself, managing the request/response cycle. Firebase removes that step and does all that work for you.

Firebase also offers a baked in authentication/authorization layer. No more managing users/login! Firebase has an entire auth package that supports anonymous login, username/password, and several OA solutions (Facebook/Twitter/Github/Google) as well. I cover this section towards the end of this article, but wanted to mention it at the start. But let’s get to some examples. 🙂

First off, I will be using JavaScript for the rest of this article. I’m a front-end developer, it’s what I know, and I think it best shows how to use Firebase. Here are a few links to bookmark first:

When you sign up, you can choose from any pricing platform, including free. The free package offers 100 concurrent connections, 1GB of storage and 10GB of data transfer. You will need an account to use Firebase, but the free package is plenty for most small projects.

Accessing Data

To get started, you just need to include add a <script> tag referencing the JavaScript file. It’s also available via bower and JSPM if you are a fan of those, or you can download right off the website.

All data is accessed via a REST API that starts with a URL provided by Firebase when you setup your account.  Since data is stored as a JSON document, you can access any data via a normalized path. For example, if you have this data structure:

{
    "users": {
        "username1": {
            "firstName": "James",
            "lastName": "McConnell"
        }
    }
}

To access the “lastName” node, your path would be <firebaseUrl>/users/username1/lastName. To start, you spin up a new Firebase reference in code:

var firebaseRef = new Firebase('<FIREBASE_URL>');

You could also create a reference right to the node you want:

var userLastNameRef = new Firebase('<FIREBASE_URL>/users/username1/lastName');

Retrieving Data

So how do we get actual data into our app? Firebase has a complete API for saving and retrieving data. To retrieve the value at the above, we use the “on” method:

var userLastNameRef = new Firebase('<FIREBASE_URL>/users/username1/lastName');
userLastNameRef.on('value', function (snapshot) {
    console.log(snapshot.val());
}, function (error) {
    console.log('There was an error: ' + error);
});

The ‘value’ that we pass as an argument will return the value at that endpoint (wrapped in an object). However, not only will this return the value, it will call your callback “function (snapshot)…” every time that value changes on the server. So if someone else is also connected to this exact node, and they push a change to that value, you are then notified of that change. Three-way data binding in action!

Another common action is ‘child_added/removed/changed/moved’  Listening to this will notify you whenever a new child is added/removed/changed/moved to or from the node. This is useful for chat-like applications where you might have a “messages” node and want to know when a new message has been added by another user so you can propagate that to all other users.

Saving Data

Saving data back is accomplished with just a couple easy to use methods. To save a new object at a specified node, you simply call “set” and supply it an object with the properties and values you want to save:

var usersRef = new Firebase('<FIREBASE_URL>/users');
usersRef.set({
    username2: {
        "firstName": "Peyton",
        "lastName": "Manning"
    }
});

And it’s easy as that. You’ve now added a new child to the users node. And if any other client was listening to the ‘child_added’ event on the users node, they will be notified of the new object and can respond appropriately.

To update data, you use the “update” method:

var username1Ref = new Firebase('<FIREBASE_URL>/users/username1');
username1Ref.update({
    "firstName": "Eli"
});

Easy as pie. 🙂 But since we live in a concurrent world, and two people try to save the same data to the same node at the same time, we need to handle that. For that, we use the “push” method, which does the same as “set” but generates a unique ID (based on a timestamp) and uses that as the key your data is saved under:

var usersRef = new Firebase('<FIREBASE_URL>/users');
usersRef.push({
    "firstName": "Tom",
    "lastName": "Brady"
});

What will happen here is that the key of the node is no longer a value that you supply. The actual data would look like something akin to this:

{
    "users": {
        "-GHRUSTY78-DJDFH778": {
            "firstName": "Tom",
            "lastname": "Brady"
        }
    }
}

In my experience, this is always the preferred method since it erases the possibility of any concurrency errors.

Security

Firebase also supplies a security layer so that you can determine whether or not a specific node can be read from/written to based on a number of different situations. You can find a more in depth explanation of the security rules here: https://www.firebase.com/docs/security/guide/securing-data.html

Let’s show some basic examples. Every database comes with a Security & Rules tab in your dashboard. This stores rules in JSON format. The specific syntax for complex security rules can be a little tricky, and does take some time to truly understand, so I will outline a few simple examples.

Rules come in three basic functions: .read, .write, and .validate. For read and write, you just set the value to true/false.

{
    "rules": {
        "users": {
            // The "users" node cannot be read by anyone
            ".read": false,
            // The "users" node is now essentially write-only. It can be written to but not read from
            ".write": true
        }
    }
}

If you were to attempt to read from the “users” node with these rules in place, this is how you would handle it in code:

// Assuming "ref" is a valid Firebase reference
ref.child('users').once('value', function (dataSnapshot) {
    // This success method will not be called
}, function (error) {
    // This will be called with "PERMISSION_DENIED" error message
});

You can also validate whether or not data should be written to a node. Given a basic chat application, you might want to ensure that data written to a “messages” node is of a certain length.

{
    "rules": {
        "messages": {
            ".validate": "newData.isString() && newData.val().length > 0 && newData.val().length < 200"
        }
    }
}

With this rule, only a string that has a length greater than 0 and less than 200 characters would be written.

You can also implement user based security, ensuring that only authorized users can read/write data. Let’s say you have a “users” node, and you want to ensure that data is only written here by authorized users, as well as only by the current user matching the unique id (you wouldn’t want to allow a user to read from or write to a node that is not theirs). This is how you would set up that rule:

{
    "rules": {
        "users": {
            // Variables that start with '$' are considered a wild card path. In this case, it represents the unique id generated when data was written to this node
            "$user_id": {
                ".write": "$user_id === auth.uid"
            }
        }
    }
}

Here, the users node can only be written to if the currently authenticated user id matches their user id.

Performance and Indexing

Performance is key for any database system, and no less for NoSQL/document database. Firebase allows you to place indexes on nodes to speed up queries. If you are querying a large node for a specific field, you can set an index on that field in the “rules” JSON from above rather easily.

I am currently working on a browser-based, multiplayer trading card game. I have a large collection of cards, and each card has a “cardType” property that I often query for so that I can retrieve only cards of a certain type. The index for this is quite simple:

{
    "rules": {
        "cards": {
            ".indexOn": ["cardType"]
        }
    }
}

You can also set an index on values, in the event that you are possibly ordering results by their value. The rule would look like this:

{
    "rules": {
        "scores": {
            ".indexOn": ".value"
        }
    }
}

There are many other ways you can index data to increase performance. Check the documentation at this link to learn more: https://www.firebase.com/docs/security/guide/indexing-data.html

One final note on performance. Since Firebase is essentially a DBaaS (database as a service), your first bottleneck will obviously be your network/internet connection. While there isn’t a whole lot Firebase can do about this, it is something to keep in mind. All communication is over the wire (using SSL for added security).

User Authentication and Authorization

One of the coolest feature (I think) that Firebase offers is a fully-fledged authentication and authorization system. It offers several options for authenticating users:

  • Email and Password
  • Anonymous
  • Custom Authentication using JWT (JSON web tokens)
  • OAuth for Facebook/Twitter/GitHub/Google

We won’t go over each one in detail here. You can learn more at this link: https://www.firebase.com/docs/web/guide/user-auth.html

To configure this, you will need to head to your dashboard and enable any providers you wish to use. They have full integration with the 4 major OAuth providers listed above. For my needs, email and password are fine, so I will show a few examples to give you an idea of how it works.

To authenticate a user with the email/password provider, your code would look like this:

// Assuming "ref" is a valid Firebase reference
ref.authWithPassword({ email: test@test.com, password: 1234test }, function (error, authData) {
    if(error) {
        // handle error
    } else {
        // Authentication was successful
    }
});

This is all well and good, but what if you have other places in your application (say, a header component) that needs to know when a user has been authenticated? You can monitor auth state using the “onAuth” method, like so:

// Assuming "ref" is a valid Firebase reference
ref.onAuth(function (authData) {
    if (authData) {
        // User is authenticated
    } else {
        // User has been logged out
    }
});

To stop listening, simply call “offAuth”:

ref.offAuth(function (authData) {... });

You can also get the current authenticated user synchronously using the getAuth() method. This is not an async call, so no need for a callback here:

var authData = ref.getAuth();
if (authData) {
    // valid user is logged in
} else {
    // no user logged in
}

To log users out, just use the “unauth” method:

ref.unauth();

By default, a Firebase session is 24 hours from when a user logs in. You can change this under the “Login & Auth” tab in your dashboard. There is a “Session Length” dropdown that you can use to change the session length.

In my opinion, the authentication/authorization system in Firebase is the most valuable. I don’t have to worry about setting up my own authentication in any app, I can let Firebase handle all of that for me.

Client-side Support

Since most client side web applications are written using different JavaScript frameworks, Firebase actually has official libraries available for most of the popular frameworks. As an Angular user myself, the AngularFire library is designed from the ground up to work within Angular as a third party service, allowing you to communicate with Firebase without having to litter your code with “$scope.$apply()” calls all over, which you would have to do without it. There are libraries also available for:

  • Ember
  • React
  • Backbone
  • Ionic

There are also frameworks available for iOS and Android development, so you can have Firebase available to your native mobile application as well. Pretty nifty. 🙂

Conclusion

I’ve been using Firebase in my personal projects for over a year now, and I couldn’t be happier with it. The ability to react to changes in my database without wiring up an entire layer of code for handling the communication has been magnificent. And while I’m a firm believer in “use only the tools you need”, I hope that you’ll take some time to check it out and see if it fits a need you might have in your projects.  Also, feel free to comment below with any specific questions.

Comments

  1. Very nicely sums up the Fiberbase basics. Thanks for the post.

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: