Quick and Dirty Comments with Firebase Realtime Database

With some help from this excellent resource, I finally implemented a comments system for my static Jekyll blog (this static Jekyll blog).

It was quite educational, so I thought I’d write a short post about it.

Firebase is a fantastic resource, but can be a pain in the neck with configurations and permissions and so on, but the short version of it all is:

  1. Make a project on Firebase, name it whatever you want to name it
  2. Click the and then select “add Firebase to web app”.

Copy the config object, make a new file called app.js, and paste it in there. For reference, here is my config object:

var firebaseConfig = {
apiKey: "YOUR-API-KEY",
authDomain: "friendlychat.firebaseapp.com",
databaseURL: "https://friendlychat.firebaseio.com",
projectId: "friendlychat",
storageBucket: "friendlychat.appspot.com",
messagingSenderId: "YOUR-ID",
appId: "YOUR-APP-ID"
};

// Initialize Firebase, first make sure there isn't already an app running
if (!firebase.apps.length){
    firebase.initializeApp(firebaseConfig)
}

Once that is done, make sure to link default.html or post.html or whatever your template file is to app.js. Also add the following scripts to your default.html or post.html page (I’m assuming your post.html uses the layout of default.html, make appropriate considerations if not):

<script src="https://cdn.firebase.com/js/client/2.2.1/firebase.js"></script> 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>

Let us now work on initializing a new Firebase database, this can be done like so: const ref = firebase.database().ref().

According to the Firebase docs, “data stored in a Firebase Realtime Database is retrieved by attaching an asynchronous listener to a database reference”. The ref variable is our listener.

Unlike relational databases, the Firebase Realtime Database is a NoSQL database, and is therefore reliant on a document-collection interface. A collection is a series of documents, and documents can link both to other documents or to sub-collections, or they contain text entries. Filepaths can also be stored.

You can think of a NoSQL database as a tree with branches and leaves. But it’s a weird tree because leaves branch to other leaves. That might not have been the best metaphor.

Okay, we now need to name our branches in an appropriate manner. A convenient way to name them is by using the window.location.pathname attribute of our HTML page. This won’t change unless you change the name of the HTML page (and why would you do that?).

But the pathname often contains weird characters like & that could confuse our NoSQL database. So we clean up the string with this function (and regular expressions):

const makeClean = (text) => {
    return text.toString().toLowerCase().trim()
        .replace(/&/g, '-and-')
        .replace(/[\s\W-]+/g, '-')
        .replace(/[^a-zA-Z0-9-_]+/g,'');     
}

Okay, finally, we can create the branch (or access the branch if it was already created) using postRef: let postRef = ref.child(makeClean(window.location.pathname)).

Let’s whip up a UI really quick. Go to your equivalent of post.html, and paste in the following tried-and-tested form code:

<form id="comment">
    <input type="text" id="message" placeholder="penny for your thoughts">

    <input type="text" id="name" placeholder="Name">
    <input type="text" id="email" placeholder="Email (optional)">
    <input type="submit" value="Post Comment">
</form>
<div class="comments"></div>

I didn’t like the way this rendered things, so I fixed it with a little alignment CSS magic in the top of post.html:

<style>
#imageStuff{
display: inline-block;
}

#actualText{
display: inline-block;
padding-left: 1%;
}

#comment {
padding: 5%;
}

#message {
width: 30%;
line-height: 20%;
}
</style>

Everything looks nice and pretty now, you should be seeing something like this: comments

I had heard about jQuery quite a bit beforehand, but I realized just how useful it can be for DOM manipulation and accessing elements while working on this project. Accessing an element with id="comment" is as simple as $("#comment"). Programming a workflow for the submit event is as simple as writing a function to run when that event is triggered:

$("#comment").submit(() => {
    let name = $("#name").val()
    let message = $("#message").val()
    let number = Math.floor(Math.random()*1000) // for the profile picture
    if(name && message){
        postRef.push().set({
            name: $("#name").val(),
            message: $("#message").val(),
            email: $("#email").val(),
            avatar: number,
            postedAt: Firebase.ServerValue.TIMESTAMP
        });
    }
    // clear your text fields:
    $("input[type=text], textarea").val("");
    return false;
});

Now when you look at the Firebase docs, you’ll see that the child_added event is fired each time a child is pushed onto the database (think of it as each time a node is added). We can exploit that fact to update our list of comments each time, using jQuery again.

postRef.on("child_added", (snapshot) => {
    let newPost = snapshot.val();
    $(".comments").prepend('<div class="comment">' +
    '<img src="http://www.gravatar.com/avatar/' + (newPost.avatar) + '?s=100&d=retro" height="20" id="imageStuff"/><h4 id="actualText">' + (newPost.name) + ': ' + (newPost.message)  + '</h4></div>');
});

And that’s about all there is to it. Firebase can be annoying at times, but overall, I think it is a great product.

Another option to implement a comments system that would also serve to be effectively free is through a comments.json file on the same server. Assuming that you will not be dealing with millions of comments, lookup times can actually be pretty small. If there is enough interest, I could make another blog post to explain the implementation of a simple json comments file.