Database Operations on the Firebase Realtime Database using the JS SDK
Tags: firebase, javascript, nosql, node
Published: 2020-04-28 16:07:15

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:

```javascript
{

  "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.

```javascript
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.

```javascript
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.

```javascript
const firebaseAdmin = require('firebase-admin')

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

var 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.“_

```javascript
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.

```javascript
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.

```javascript
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.

```javascript
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.

```javascript
{

  "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.

```javascript
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.

```javascript
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.

```javascript
{
    "-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`

```javascript
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
Firebase Database Admin Docs – https://firebase.google.com/docs/database/admin/start