Jumping from SQL to Firebase NoSQL Database

This is a guest post by Jorge Vergara who has written many tutorials about using Firebase in Ionic apps. In this post he writes about how to structure and update your data in a NoSQL database like Firebase.

As a new Firebase user, you're probably still trying to wrap your head about all the querying possibilities you used to have when working with MySQL or PostgreSQL databases.

SQL databases use tables to store data, those tables have columns and rows where the data is stored.

SQL Table example

A column is basically a property of the table.

For example, a table could have 3 columns: ID, First Name, Last Name.

And the rows are the records inserted, so if the columns were id, first name, and last name, then the first row in my case would be "ebrattJorge, Jorge, Vergara".

Where:

  • ebrattJorge is my ID.
  • Jorge is my first name.
  • Vergara is my last name.

If I want to add my email I can't just send it with the rest of the data ebrattJorge, Jorge, Vergara, j@javebratt.com that won't work, since the email column doesn't exist on our table.

I'd have to modify the table to accept the new value, this has its pros too, for example, it's making sure data integrity remains throughout the entire database.

Firebase (and mostly all NoSQL databases) on the other hand don't have this kind of restrictions.

The Firebase database is just a big JSON object where you can store whatever you want inside. Unlike SQL there's no schema for the database, no tables, no columns, it's just a combination of key/value pairs.

For example, our user table in Firebase would look like this instead:

{
  users: {
    "ebrattJorge": {
      "first_name": "Jorge",
      "last_name": "Vergara",
      "email": "j@javebratt.com"
    }
  }
}

Basically, you can just add whatever information you want to add to the user's node. This is really great for flexibility but doesn't really help us ensure data integrity. So you take the good with the bad.

In this post, I'm going to do my best to guide you in how to structure your data in Firebase, so allow ultra fast reads, minimal querying, and how you'll accomplish a common SQL query just restructuring your data.

We'll break it into 3 parts:

  • Best practices for the data structure.
  • A practical example of who to structure the data.
  • A way to ensure data integrity when updating.

Firebase Best Practices

One of the best ways to ensure your app is going to stay performant is to keep your database connection "clean", meaning that there isn't too much clutter, too many database calls for things that you don't really need to.

With that in mind, let's jump right into best practices:

READ > WRITE

You should be thinking by default on how you're going to be reading the data you're about to store in the database.

Statistically speaking, the average user is going to spend more time reading data from the database than writing it, so you'll want to make sure reading the data is as painless and uses as little resources as possible.

For example, if you're building an event management app (...which I once had to do for my wife :P ) you might be thinking about how to store your users, the events you're creating, and the relationship between them.

You might be thinking something like this:

{
  users: {
    "ebrattJorge": {
      "first_name": "Jorge",
      "last_name": "Vergara",
      "email": "j@javebratt.com"
    }
  },
  events: {
    "natlPizzaDay": {
      "eventName": "National Pizza Day Celebration",
      "eventDate": "03/21/2017",
    }
  }
}

And that's a good start, but how do we relate the event attendees with the event? For example, let's say that you need to list everyone who went to the "National Pizza Day Celebration".

Then you can do something like this inside the events node:

events: {  
  "natlPizzaDay": {
    "eventName": "National Pizza Day Celebration",
    "eventDate": "03/21/2017",
    "guestList": {
      "ebrattJorge": true
    }
  }
}

We're simply adding the user's id as a key with the value equal to true so we know the user with the ID of "ebrattJorge" went to the event

This is actually a great start, but if you want to get more information about these users (and you'd probably want) you'd have to use the user's ID to query his information.

How do we avoid that extra query?

DE-Normalization

If you're coming from an SQL background you probably know about normalization (avoid duplicates, structure tables, etc).

Firebase has a different flow, and it's DE-Normalization, where it is actually OK (in fact, a good practice) to duplicate data.

How much data do I duplicate? Honestly? I have no idea.

It depends on your database, your goals, and especially on your view, for example:

You have an event view, where you're seeing the information about that event, inside the view, you have a guest list, that shows just that, a list of the guests who attended the event.

You're probably not going to need much information about the guests at that point, so instead of doing an extra query, you could do something like this:

events: {  
  "natlPizzaDay": {
    "eventName": "National Pizza Day Celebration",
    "eventDate": "03/21/2017",
    "guestList": {
      "ebrattJorge": {
        "first_name": "Jorge"
        "last_name": "Vergara"
      }
    }
  }
}

That way you have all the data you need about the guests, so you won't have to call the database a second time to display the guest list.

You can go a step further and do the same thing to people so that you can always have a list of which events has a user attended.

users: {  
  "ebrattJorge": {
    "first_name": "Jorge",
    "last_name": "Vergara",
    "email": "j@javebratt.com",
    "events": {
      "natlPizzaDay": {
        "name": "National Pizza Day",
      }
    }
  }
}

This is a great way to start de-normalizing data, and for most use-cases, you can just leave it like this, but if you think your app will be "big" then there's a catch here.

Firebase uses references to pull your data, for example, if you want to pull the "National Pizza Day" event you'll do something like this:

let eventRef = firebase.database().ref("events/natlPizzaDay");  

That will bring the entire event information, so if all you need is the event name + date then this is not a really great idea, you're going to be pulling a ton more data than you need to if those inside lists start growing.

But don't worry, there's a way to go around this.

Keeping your data flat

Firebase supports deep nesting (up to 32 levels I think) for your data, but it's a really bad idea to go that deep, imagine only needing the event name and pulling instead the name + a list of 5,000 people that attended to the event (and National Pizza Day? More like 5,000 million people)

If you're coming from SQL, imagine this as a "Many to Many" relationship, where you create an extra table to store the relationship.

For example, let's go back to our event/user relationship, what if instead of storing the guestList inside the event node we create a separate table for it?

events: {  
  "natlPizzaDay": {
    "eventName": "National Pizza Day Celebration",
    "eventDate": "03/21/2017"
  }
},
eventGuestList: {  
  "natlPizzaDay": {
    "ebrattJorge": {
      "first_name": "Jorge"
      "last_name": "Vergara"
    }
  }
}

That way if you're creating a detail page for the event you can do 2 Firebase calls independent from each other:

let eventDetailRef = firebase.database().ref("events/natlPizzaDay");  
let eventGuestListRef = firebase.database().ref("eventGuestList/natlPizzaDay");  

Those calls will happen independently and you can choose where or how to run/return them.

This way you're only pulling the data you need from the database. By the end the structure would look something like this:

events: {  
  "natlPizzaDay": {
    "eventName": "National Pizza Day Celebration",
    "eventDate": "03/21/2017"
  }
},
eventGuestList: {  
  "natlPizzaDay": {
    "ebrattJorge": {
      "first_name": "Jorge"
      "last_name": "Vergara"
    }
  }
},
users: {  
  "ebrattJorge": {
    "first_name": "Jorge",
    "last_name": "Vergara",
    "email": "j@javebratt.com",
  }
}

This gives you the best flexibility by far, but there's a small catch (and by small I mean really big), what happens if you get my name wrong?

What if you need to update someone's name? In SQL this is painless because you just update the user's name and updates start happening magically all over your app.

If you go into users/ebrattJorge/ and change "first_name": "Andres" it won't affect anything other than that node, meaning you'll end up with something like this:

eventGuestList: {  
  "natlPizzaDay": {
    "ebrattJorge": {
      "first_name": "Jorge"
    }
  }
},
users: {  
  "ebrattJorge": {
    "first_name": "Andres",
  }
}

And that's just a big fat NO, you can't allow that to happen, for that you can manually update every other instance where the user's name is (and by manually I mean you can create a function for it), so it would be something like:

firebase.database().ref('users/ebrattJorge').update({  
  "name" : "Andres"
});

firebase.database().ref('eventGuestList/natlPizzaDay/ebrattJorge').update({  
  "name" : "Andres"
});

That's OK I guess, the problem is that you have no idea what the user's Internet connection is like, what if you have to update the name in several places and when the app has updated 2 nodes there's a power outage?

Then you will have a few updated and a few outdated nodes, which means you'll be jumping manually (and this time I DO mean manually) to your database and one by one updating those names.

You know better, your time is too valuable for that.

For those cases Firebase has a cool feature, it's called multi-part updates, basically it's one function where you chain every single update you need to happen and Firebase treats it like ONE update operation.

If all of the updates clear, then the function will return a success, if somewhere through the updates it fails, it will rollback and won't change anything, that way you can try it again when power/the Internet is back.

This is when we try to get a bit of that data integrity that SQL databases provide :)

Let me walk you through a step by step of how this update would happen in our little example:

First, you'd need to create a reference to your root node:

const rootRef = firebase.database().ref('/');  

After we have that, we'll create an object to store our updates:

let updatedUserName = {};  

And then we'll start to add the updates to that object:

updatedUserName["users/ebrattJorge/first_name"] = "Andres";  
updatedUserName["eventGuestList/natlPizzaDay/ebrattJorge/first_name"] = "Andres";  

And then we'll tell our rootRef to run those updates.

rootRef.update(updatedUserName, (error) => {  
  if(error){ console.log(error) }
});

That's an atomic operation (kinda like Git commits) so it will either update all the paths or none of them.

Next Steps

By now you should have a better understanding of how data is structured on Firebase, your next steps should be to actually put this into practice while building your next Ionic and Firebase app.

I've created a little care package to help you get started building those apps, in includes:

  • A free 7-lesson course that takes you through building your first app with Ionic and Firebase (setup, authentication, objects, lists, Firebase Storage, and more) using the JavaScript SDK.

  • A free 5-lesson course to help you get started with AngularFire2, a really cool library built by angular and Firebase team members, which lets you use real-time bindings and observables in your Ionic + Firebase app without any heavy lifting.

  • Access to a code library where I have the source code of over 10 apps that I've built while creating tutorials for my blog (form validations, email authentication, ionic native, and more).

CLICK here to claim the bonuses.

comments powered by Disqus