Building a Slackbot Clone using Firebase Realtime Database & Vue.js - Part 1

Tuesday, March 14, 2017

Slack bot is really useful at getting things done easier for the user. This is a three part series of how to build a slack bot like feature using the Firebase Realtime Database and Vue.js. This is not a tutorial for building a bot inside of Slack, but a standalone one with an aim to teach you about the Firebase Realtime Database and Vue.js Reactivity and how to go about building such a feature.


Overview

Following is the overview of the various components involed. We use the Firebase Realtime Database for storing the data, Vue.js to render the UI.

Slackbot clone Overview

Firebase Authentication

This application also includes the Firebase Authentication which you can read about in the previous post Implementing Firebase Authentication. The same implementation of Authentication is used in this application as well. The previous post explains how to create a Firebase application, setting up the Authentication and Authenticating users.

1. Firebase Realtime Database

The Firebase Realtime Database is a cloud-hosted database. Data is stored as JSON and synchronized in realtime to every connected client.

The Realtime Database provides a declarative rules language that allows you to define how your data should be structured, how it should be indexed, and when your data can be read from and written to.

Official Doc

1.1 Setting up Realtime Database

Using the Realtime Database we can store the data using a JSON like format. To use the Realtime Database, we need to configure the database URL while initializing the Firebase config by specifying the database URL from the Firebase Console.

It will have this format - https://<databaseName>.firebaseio.com

var config = {
    apiKey: "apiKey",
    authDomain: "projectId.firebaseapp.com",
    databaseURL: "https://databaseName.firebaseio.com",
    storageBucket: "bucket.appspot.com"
  };

firebase.initializeApp(config);

Once the configuration is setup, we can get the reference to the Realtime Database using firebase.database() method.

// Get a reference to the realtime database
var database = firebase.database();

1.2 Overview of Realtime Database

The Realtime Database synchronizes with all the clients that is connected to it. Any kind of modification to the data from a client will reflect to all the other clients via events with which the client side code decides what to do corresponding to that event.

Note: By default, the read and write operations are permitted only to Authenticated users. These rules can be changed in Database sections Rules Tab. To know more on how to configure those rules read here.

1.2.1 Getting reference

Any operations like Creating, Updating, Deleting or Listening to changes for a data requires a reference to that particular piece of data, which is an instance of firebase.database.Reference().

Consider the following example database structure,

-posts: List
  -{postId}
    -title
    -description
    -author
    -comments: List
      -{comment1Id}
        -comment
      -{comment2Id}
        -comment

The post has few data properties and the comments which is a List(posts is itself a List) of data object. To update a data on post, we would need a reference to the post data and to add a new comment to the comments List, we would need a reference to the comments of the post data item.

To get a reference to a piece of data, we should use the firebase.database().ref() method. The method must be passed a string construct, which must be constructed by traversing the JSON structure, separated with /.

So to get the reference to a post,

var postRef = firebase.database().ref('posts/'  + postId);

and to get a reference to the comments,

var commentsRef = firebase.database().ref('posts/' + postId + '/comments');

finally to get a reference to a particular comment,

var commentRef = firebase.database().ref('posts/' + postId + '/comments/' + commentId);

Once we get a reference to a data item or List, we can modify and add items to it based on the data property.

1.2.2 Basic CURD Operations

First, we need to get a reference to the data item that we need to perform the operation on.

var postRef = firebase.database().ref('posts/'  + postId);
var commentsRef = firebase.database().ref('posts/' + postId + '/comments');
var commentRef = firebase.database().ref('posts/' + postId + '/comments/' + commentId);

Create

To save a new record to a reference, use the set() method.

// Create
postRef.set({
  title: 'This is a sample title',
  description: 'This is a small description',
  author: 'codedodle'
});

var commentRef = commentsRef.push();
commentRef.set({
  comment: 'This is a sample comment'
});

Using the set method, we can add a new data of the referenced item. When the push() method is used, it creates a new auto generated unique id for the data item and we can use that to set the value for that item. Above we can see how the commentsRef is used to push and create a new comment and update the value for comment using commentRef reference.

Update

To update a data item, use the update() method.

// Update
postRef.update({
  title: 'Updated sample title',
  description: 'Updated small description',
  author: 'codedodle'
});

Passing the updated value to the update method upon the reference updates the referenced value.

Read

To read a value of a reference, we need to listen to the value event using either the once() method, if you need the value to be loaded only once and on() method, if you need the value to be loaded when it is updated.

// Read
// Load only once
postRef.once('value').then(function (snapshot) {
  var title = snapshot.val().title;
});

// Load whenever the value is updated
postRef.on('value', function (snapshot) {
  var title = snapshot.val().title;
});

Delete

To delete a data, you just call the remove() method on the reference that needs to be deleted.

// Delete
postRef.remove();

1.2.3 Working with List of Data

Data reference is always not a single object, sometimes we have reference to an List(Array) of data. When it’s a list of data, we can listen to events that updates the client whenever the element from the list is added, removed or modified.

To listen to such event(s) of a List reference, we can use the on() method. Events such as the child_added, child_removed orchild_changed are triggered whenever the child elements of the list are added, removed or changed.

var commentsRef = firebase.database().ref('posts/' + postId + '/comments');

// Triggered whenever a new child is added to the list.
commentsRef.on('child_added', function(data) {
  addCommentElement(postElement, data.key, data.val().text, data.val().author);
});

// Triggered whenever a child is changed.
commentsRef.on('child_changed', function(data) {
  setCommentValues(postElement, data.key, data.val().text, data.val().author);
});

// Triggered whenever a child is removed from the list.
commentsRef.on('child_removed', function(data) {
  deleteComment(postElement, data.key);
});

2. Realtime Database Structure

Here we are going to look into the database structure(JSON Tree) of the application we are building. There are some best practices(like Avoiding Nesting of data, Creating flat data structure and scaling) for designing database structure for the Realtime Database which you can read it here.

We need to be able to store two things in the database,

  • Individual User Messages
  • Available Commands

To store the Individual User Messages we’ll have the following format.

-chats: List
  -{userId}
    -messages: List
      -{messageId}
        -cmd: One of the available commands
        -m_type: [cmd | msg]
        -text: Text to be displayed in message
        -timestamp: Message Created Timestamp
        -data: Object - Varies with command
      -{messageId}
        -cmd: One of the available commands
        -m_type: [cmd | msg]
        -text: Text to be displayed in message
        -timestamp: Message Created Timestamp
        -data: Object - Varies with command
  -{userId}
    -messages: List
      -{messageId}
        -cmd: One of the available commands
        -m_type: [cmd | msg]
        -text: Text to be displayed in message
        -timestamp: Message Created Timestamp
        -data: Object - Varies with command

User Messages Database Structure

Available commands have the following format.

-commands:
  -commandName:
    -cmd: The Command i.e "/{commandName}"
    -desc: A small description of the command
  -commandName:
    -cmd: The Command i.e "/{commandName}"
    -desc: A small description of the command

Commands Database Structure

3. Realtime Database Messages Utility

Using the firebase realtime database APIs discussed above, following is a utitlity written to Add, Remove and Update a message for the currently logged in User.

/**
 * A small wrapper utility around the firebase database for the 
 * application's messaging functionality.
 */
var fireUtil = {

  /**
   * Add a new message into the firebase realtime database
   *
   * @param Object data
   * @return boolean
   */
  addMessage: function addMessage(data) {
    if (firebase.auth().currentUser === null && data !== null) {
      return false;
    }

    var user = firebase.auth().currentUser,
      messagesRef = firebase.database().ref('chats/' + user.uid).child('messages');
      newMsgRef = messagesRef.push();

    newMsgRef.set(data);

    return true;
  },

  /**
   * Remove a message from the firebase database for the current user.
   * 
   * @param String msgId
   * @return boolean
   */
  removeMessage: function removeMessage(msgId) {
    if (firebase.auth().currentUser === null && msgId !== null && msgId !== '') {
      return false;
    }

    var user = firebase.auth().currentUser,
      messageRef = firebase.database().ref('chats/' + user.uid + '/messages/' + msgId);

    messageRef.remove();
  },

  /**
   * Update a message in the firebase database for the current user.
   *
   * @param String msgId
   * @param Object data
   * @return boolean
   */
  updateMessage: function updateMessage(msgId, data) {
    if (firebase.auth().currentUser === null 
      && msgId !== null && msgId !== '' && data !== null) {
      return false;
    }

    var user = firebase.auth().currentUser,
      messageRef = firebase.database().ref('chats/' + user.uid + '/messages/' + msgId);

    messageRef.update(data);    
  }

};


Next - Building slack bot clone using Firebase and Vue.js - The Bot - Part 2