Google Firestore

Google Firestore (also called Google Cloud Firestore or Cloud Firestore) is a cloud-hosted NoSQL database that is hosted on Google's servers. We can use Google Firestore instead of MongoDB.

Google Firestore is part of Google Firebase. In order to use Google Firestore on the server-side, you need to:

  1. Connect your computer to Firebase
  2. Connect your Nodejs server-side to Firebase

1. Connect Your Computer to Firebase

You need to connect your computer to Firebase before you can connect our nodejs project's server-side to Firebase. Follow the steps below to connect your computer to Firebase.

Install firebase-tools, as shown in the code below:

npm install -g firebase-tools

Install firebase-tools on your computer.

 

After installing firebase-tools, run the command firebase login. This connects your computer to Firebase and grants you access to your Firebase projects:

firebase login

Login to Firebase.

 

You can test that your local machine is connected to Firebase by running the command below. This will show the list of your Firebase projects.

firebase projects:list

List the Firebase projects that are on your computer.

 

You can follow the link below if you wish to read more details about connecting your computer to Firebase:

https://firebase.google.com/docs/cli#:~:text=The%20firebase%20init%20command%20creates,deployed%20to%20your%20Firebase%20project

 

2. Connect Your Nodejs Server-side to Firebase

We need to set up Google Firestore to run on the server-side of our project, so that our routes will access it rather than MongoDB. In order to use Firebase we need to run the command firebase init in the project's root folder.

When we run the command firebase init in the project's root folder, we get the screen below.

 

 

Anser yes to the questions "Are you are ready to proceed?"

 

 

Select the Firestore: Deploy rules and create indexes for Firestore option.

 

 

You will then be asked to associate your project directory with a Firebase project. Select the option to Use an existing project

Firebase will give you the option to select the Firestore project that you previously set up in Google Firestore. In the example below, my project is called dkit-full-stack (DkIT Full Stack)

 

 

When prompted to select the file that should be used for Firestore Rules, you should choose the default file (firestore.rules)

 

When prompted to select the file that should be used for Firestore indexes, you should choose the default file (firestore.indexes.json)

 

 

After you initialise Firebase, the files that are highlighted below will have been added to your root folder.

Check that the files have been installed in your project's root folder.

Generating a Firestore Key for use on the Server-side

Go to https://console.firebase.google.com/

Your firebase projects will be listed.

Select your Firestore project.

Server-side Authentication

Before we can access a Firestore database from a server, we must authenticate the server with Firebase. we authenticate a server with a service account. Note that this is different to how authentication works in client-side apps, where we would sign in with a user account's credentials. The service account is downloaded from Google as a json file, as discribed below:

Within your project window, you need to click on the Project Overview settings ⚙icon.

Within the settings, select the Project Settings option

 

 

Within the Project Settings window, select the Service accounts option.

Pressing the Generate new private key button will generate and download a json file that contains details of the private key.

 

 

For our project, we shall rename the private key to be serviceAccountKey.json and store it in the server/config folder.

The serviceAccountKey.json file will look similar to code shown below

server/config/serviceAccountKey.json

{
"type": "service_account",
"project_id": "your project id will be generated by Google",
"private_key_id": "your private key id will be generated by Google",
"private_key": "-----BEGIN PRIVATE KEY-----your private key will be generated by Google-----END PRIVATE KEY-----\n",
"client_email": "your client email will be generated by Google",
"client_id": "your client id will be generated by Google",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-3elsa%40dkit-full-stack.iam.gserviceaccount.com"
}

 

The serviceAccountKey.json is needed to connect to the Firestore database. We overwrite the original MongoDB contents of the server/config/db.js file, as shown below:

server/config/db.js

// Firestore
const admin = require('firebase-admin')
const serviceAccount = require("./serviceAccountKey.json")
admin.initializeApp({credential: admin.credential.cert(serviceAccount)})

 

We overwrite the original MongoDB contents of the server/models/cars.js file, as shown below:

server/models/cars.js

// Firestore
const admin = require('firebase-admin')
const db = admin.firestore()
   
module.exports = db.collection('cars')

 

The server/routes/cars.js file will now use the new Firestore model, so we do not need to change the code that requires the cars model, as shown below

server/routes/cars.js

const carsModel = require(`../models/cars`)  // this code is identical to the MongoDB code

Finally, we change the server-side server/routes/cars.js router endpoints to access Firestore instead of MongoDB.

Read all records

router.get(`/cars`, (req, res) => {     
    carsModel.get()
    .then(collection => 
    {
        let data = collection.docs.map(doc => 
        {
            // As the firestore document.data does not contain the document's id, we need to combine document data and document id
            let docDataAndId = doc.data()
            docDataAndId._id = doc.id
            return docDataAndId
        })
        
        res.json(data)
    })
})

Read one record

router.get(`/cars/:id`, (req, res) => 
{
    carsModel.doc(req.params.id).get()
    .then(doc =>
    {
        // As the firestore document.data does not contain the document's id, we need to combine document data and document id
        let docDataAndId = doc.data()
        docDataAndId._id = doc.id
        
        res.json(docDataAndId)
    })
})

Add new record

router.post(`/cars`, (req, res) => {
    carsModel.add(req.body)
    .then(() => 
    {
        res.json(req.body)
    })
})


Update one record

router.put(`/cars/:id`, (req, res) => 
{
    carsModel.doc(req.params.id).update(req.body)    
    .then(() => 
    {
        res.json(req.body)
    })
})

Delete one record

router.delete(`/cars/:id`, (req, res) => 
{
    carsModel.doc(req.params.id).delete()
    .then(() => 
    {
        res.json(req.body)   
    })
})

Open the mongoDB project from the previous section in these notes. Change the code so that the cars data is held in a Firestore database.

"Cars" Worked Example

The full project code for the "Cars" Worked Example that is described below can be downloaded from this link.

In the previous example, we stored the cars data in MongoDB. In this example, we shall use Google Firestore to store the car data.

Client-Side

There are no changes on the client-side, as we are just changing the resource that we access on the server-side. It was a MongoDB object in the previous example. It is Google Firestore in this example.

Server-Side

server/config/serviceAccountKey.json

{
"type": "service_account",
"project_id": "your project id will be generated by Google",
"private_key_id": "your private key id will be generated by Google",
"private_key": "-----BEGIN PRIVATE KEY-----your private key will be generated by Google-----END PRIVATE KEY-----\n",
"client_email": "your client email will be generated by Google",
"client_id": "your client id will be generated by Google",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-3elsa%40dkit-full-stack.iam.gserviceaccount.com"
}

As discussed at the top of this section, the private key is downloaded from Firebase.

server/config/db.js

// Firestore
const admin = require('firebase-admin')
const serviceAccount = require("./serviceAccountKey.json")
admin.initializeApp({credential: admin.credential.cert(serviceAccount)})

Connect to Firebase using the service account credentials that were downloaded from Firebase and stored in server/config/serviceAccountKey.json.

Once connect to Firebase, we have server-side access to the Firestore database.

server/models/cars.js

// Firestore
const admin = require('firebase-admin')
const db = admin.firestore()
   
module.exports = db.collection('cars')

Connect to the cars collection in the Firestore database.

module.exports = db.collection('cars')

server/routes/cars.js

const router = require(`express`).Router()

const carsModel = require(`../models/cars`)


// read all records
router.get(`/cars`, (req, res) => {     
    carsModel.get()
    .then(collection => 
    {
        let data = collection.docs.map(doc => 
        {
            // As the firestore document.data does not contain the document's id, we need to combine document data and document id
            let docDataAndId = doc.data()
            docDataAndId._id = doc.id
            return docDataAndId
        })
        
        res.json(data)
    })
})


// Read one record
router.get(`/cars/:id`, (req, res) => 
{
    carsModel.doc(req.params.id).get()
    .then(doc =>
    {
        // As the firestore document.data does not contain the document's id, we need to combine document data and document id
        let docDataAndId = doc.data()
        docDataAndId._id = doc.id
        
        res.json(docDataAndId)
    })
})


// Add new record
router.post(`/cars`, (req, res) => {
    carsModel.add(req.body)
    .then(() => 
    {
        res.json(req.body)
    })
})


// Update one record
router.put(`/cars/:id`, (req, res) => 
{
    carsModel.doc(req.params.id).update(req.body)    
    .then(() => 
    {
        res.json(req.body)
    })
})


// Delete one record
router.delete(`/cars/:id`, (req, res) => 
{
    carsModel.doc(req.params.id).delete()
    .then(() => 
    {
        res.json(req.body)   
    })
})

module.exports = router

Replace the MongoDB server-side cars router endpoints with code to access the Firestore database.

server/models/users.js

// Firestore
const admin = require('firebase-admin')
const db = admin.firestore()
   
module.exports = db.collection('users')

Connect to the users collection in the Firestore database.

module.exports = db.collection('users')

server/routes/users.js

const router = require(`express`).Router()

const usersModel = require(`../models/users`)


// IMPORTANT
// Obviously, in a production release, you should never have the code below, as it allows a user to delete a database collection
// The code below is for development testing purposes only 
router.post(`/users/reset_user_collection`, (req,res) => 
{
    // delete all users from collection
    usersModel.get().then(documents => 
    {
        documents.forEach(document =>
        { 
            usersModel.doc(document.id).delete() 
        })

        // add administrator user
        const adminPassword = `123!"£qweQWE`

        usersModel.add({name:"Administrator",email:"admin@admin.com",password:adminPassword})
        .then(() => 
        {
            res.json(req.body)
        })        
    })
})
           

router.post(`/users/register/:name/:email/:password`, (req, res) =>
{
    // If a user with this email does not already exist, then create new user    
    usersModel.where('email', '==', req.params.email).get()
    .then(documents =>
    {
        let numberOfRecordsRead = 0
        documents.forEach(document =>
        { 
            numberOfRecordsRead++
        })
                  
        if (numberOfRecordsRead !== 0)
        {
            res.json({errorMessage: `User already exists`})
        } 
        else
        {            
            usersModel.add({name: req.params.name, email: req.params.email, password: req.params.password})
            .then(() => 
            {
                res.json(req.body)
            })            
        }
    })
})


module.exports = router

Replace the MongoDB server-side users router endpoints with code to access the Firestore database.

 
<div align="center"><a href="../versionC/index.html" title="DKIT Lecture notes homepage for Derek O&#39; Reilly, Dundalk Institute of Technology (DKIT), Dundalk, County Louth, Ireland. Copyright Derek O&#39; Reilly, DKIT." target="_parent" style='font-size:0;color:white;background-color:white'>&nbsp;</a></div>