Database Operations on the Firebase Realtime Database using the JS SDK

Tuesday, April 21, 2020

by  Etin Obaseki  Etin Obaseki

Google’s Firebase allows us the ability to run our backend infrastructure without actually managing said infrastructure.

Some of the services Firebase offers include Cloud Functions for running backend code, Authentication and Databases

There are two database offerings in the Firebase console: The real-time database and the cloud database. This article is focused on basic operations on the real time database.

The Realtime Database

The Firebase Realtime Database is a managed NoSQL database service. As with other Firebase services, we do not need to worry about managing the underlying infrastructure or resources.

Being a NoSQL database, the data is not stored in a relational (tabular) form but instead uses a document model in JSON format. Data is stored in key-value pairs.

The Realtime Database showing the root node and children nodes

This same data represented in JSON would be as shown below:

{

  "games" : {

      "-M5GU2j383E8MaXXbLT3" : {

      "current_quesion" : 0,

      "latest_tweet" : "1251784979954241536",

      "start_tweet" : "1251784906830733315",

      "users" : {

        "AnxiousEtin" : 0,

        "ObasekiEtinosa" : 0,

        "tetrazoid" : 0

      }

    }

  }

}

The root node (trivyeah-twitter-client) contains the child node games which holds -M5GU2j383E8MaXXbLT3 as a child. That node has several nodes with concrete values (such as current_question with 0) but also contains a node users with children of its own.

References and Paths

Interaction with the database is done via references. A reference is a pointer to a particular node on the database. This reference will allow use access the data stored on that node, it’s children and perform operations on the node.

Using the firebase-admin package, we’ll get a reference to the DB.

const firebaseAdmin = require('firebase-admin')

firebaseAdmin.initializeApp();
var db = firebaseAdmin.database().ref()

db now holds a reference to the root, in the above example trivyeah-twitter-client, of our database and we can perform any operations on it.

If we instead wanted a reference to some other node on the document, we would pass a path to the desired node to the ref() method.

const firebaseAdmin = require('firebase-admin')

firebaseAdmin.initializeApp();
var gameDb = firebaseAdmin.database().ref('games')

The above code would give us a reference to the games node. To get a more deeply nested node pass the path separated by slashes e.g ref('games/-M5GU2j383E8MaXXbLT3/users')

We can also achieve this by using the child() method on any database reference. By passing in a path to any child node we want, we can get a reference to that node.

const firebaseAdmin = require('firebase-admin')

firebaseAdmin.initializeApp();
var gameDb = firebaseAdmin.database().ref('games')

car childNodeUsersDb = gameDb.child('games/-M5GU2j383E8MaXXbLT3/users')

Database Operations

The four basic functions of persistent storage are Create, Read, Update and Delete. Let’s look at each of these operations on the Firebase Realtime Database.

Create Operations

Create operations persistent a new record to storage. In the Realtime Database the set method is the basic write operation. There are two different ways of using it and will be shown here.

(Over)Writing to a Path

Using the set method will write data to the location specified, if there was any data at that location, it will be overwritten.

It takes any JavaScript value as its first argument and this value will be persisted. From the official documentation, “You can pass set a string, number, boolean, null, array or any JSON object.

const firebaseAdmin = require('firebase-admin')

firebaseAdmin.initializeApp();
var foodDb = firebaseAdmin.database().ref('breakfast')

foodDb.set({
    cereal: {
        calories: 3,
        price: 50,
        comment: "Great for when you're in a hurry"
    }
    fufu: {
        calories: 12,
        price: 10,
        comment: "Cheap, but long lasting. Really long"
    }
})

This example above sets everything at the breakfast node to the object we passed in. Anything that may have been there before is overwritten.

Adding Data to a Node

To add data to a node that already contains data without overwriting the data already on the node, we use the push() method. It returns a reference to a newly created child node on the originally referenced node.

const firebaseAdmin = require('firebase-admin')

firebaseAdmin.initializeApp();
var mealsDb = firebaseAdmin.database().ref('meals')

let newMeal = mealDb.push() //newMeal will hold the reference to the new record and can be set without overwriting it's siblings

newMeal.key 
//returns the key for the new record. Something like "-M5GU2j383E8MaXXbLT3"

We may then use the set() method on this new reference.

const firebaseAdmin = require('firebase-admin')

firebaseAdmin.initializeApp();
var mealsDb = firebaseAdmin.database().ref('meals')

let newMeal = mealDb.push()

newMeal.set({
    timeOfDay: "morning",
    mealEaten: "fufu"
    stomachStatus: "bloated",
    caloriesGained: 30
})

If you don’t need the reference of the new node for any other operations, you can chain the push() and set() calls.

const firebaseAdmin = require('firebase-admin')

firebaseAdmin.initializeApp();
var mealsDb = firebaseAdmin.database().ref('meals')

let newMeal = mealDb.push().set({
    timeOfDay: "morning",
    mealEaten: "fufu"
    stomachStatus: "bloated",
    caloriesGained: 30
})

Cases where you need to append to a node using your own key is considered an update action. Let’s look at what those are like.

Update Operations

Update operations append data to the reference specified without overwriting any other properties.

{

  "meals" : {
    "-K5GU2p242E8MaXHbQT1" : {
          timeOfDay: "morning",
          mealEaten: "fufu"
          stomachStatus: "bloated",
          caloriesGained: 30
      },
    "-K5GU2r322X8YadRZQT1" : {
          timeOfDay: "afternoon",
          mealEaten: "cornflakes"
          stomachStatus: "slightlyFull",
          caloriesGained: 2
      }
  }

}

Assuming we had the above in our database and we intend to add to the meals node without affecting any of its existing children, we would pass an object containing our desired key to the update() method.

const firebaseAdmin = require('firebase-admin')

firebaseAdmin.initializeApp();
var mealsDb = firebaseAdmin.database().ref('meals')

let newMeal = mealDb.update({
    uniqueMealKey: {
        timeOfDay: "evening",
        mealEaten: "nightcap"
        stomachStatus: "light",
        caloriesGained: -4
    }
})

Read Operations

The JS SDK supports non-blocking reads and has several events that prompt a read. The on() method takes the event we want to listen to as the first argument and a callback where we can access a snapshot of the data as the second argument.

Note: The callback we pass to the on() method is fired every time our event takes place but if you only want to perform the read and the associated callback a single time then use the once() method instead. Both methods have identical signatures.

The events we can perform read on are:

  • “value”
  • “child_added”
  • “child_changed”
  • “child_removed”
  • “child_moved”

Sticking with our meals node, we can retrieve all the meals using the value event.

const firebaseAdmin = require('firebase-admin')

firebaseAdmin.initializeApp();
var mealsDb = firebaseAdmin.database().ref('meals')

mealDb.once("value", function (snapshot) {
        let meals = snapshot.val()
    }
})

on()/once() is an asynchronous method and accepts a callback.

When the database call is complete, the callback is fired and passed a DataSnapshot instance. This object contains the state of the database at the moment that the event ("value" in this case) was fired. We can call the val() method on the snapshot to get a JavaScript object (or primitive value) representing the values of the node reference that the event was called on.

{
    "-K5GU2p242E8MaXHbQT1" : {
        timeOfDay: "morning",
        mealEaten: "fufu"
        stomachStatus: "bloated",
        caloriesGained: 30
    },
    "-K5GU2r322X8YadRZQT1" : {
        timeOfDay: "afternoon",
        mealEaten: "cornflakes"
        stomachStatus: "slightlyFull",
        caloriesGained: 2
    },
    "uniqueMealKey": {
        timeOfDay: "evening",
        mealEaten: "nightcap"
        stomachStatus: "light",
        caloriesGained: -4
    }
}

We would get the above JavaScript object in our meals variable and could then carry out what ever actions we wanted on it.

On the DataSnapshot instance, there are other methods available. You can view them in the documentation here.

Delete Operations

We have covered removing data from a node in passing earlier in the article. To delete a node, simply use the set() method on its reference to set it to null

const firebaseAdmin = require('firebase-admin')

firebaseAdmin.initializeApp();
var mealsDb = firebaseAdmin.database().ref('meals')

mealDb.child('uniqueMealKey').set(null)

Using the child() method to get a reference to the child node on meals we want to delete, we then call set() on that reference and pass it null. This will remove all the data on uniqueMealKey.

Conclusion

These are how the basic DB operations are performed on the Firebase Realtime Database using the Firebase JS SDK. The SDK is also available in Java, Go and Python. Although, the Go and Python SDKs perform blocking reads.

References

  1. Firebase Database Admin Docs – https://firebase.google.com/docs/database/admin/start