Firebase Multi-path Updates — Updating Denormalized Data in Multiple Locations

TLDR — When using .update to change data in multiple locations with a single call, .update acts as .set. If you are updating only some of the children of a node, you have to provide the entire path to each child that you want to change. This is confusing because .update works differently when updating a single location (you can pass an object and only the children you specify will be updated).

Note: This was written on 2/24/2017 and may have change since then.

Let’s take a look at the examples that Firebase provides us to see why this is confusing. In the Firebase blog about multi-location updates, they provide this example of updating a single node and changing some children and leaving others alone:

Screenshot from the Firebase Blog https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html

You can see that it is possible to update certain children of a node without affecting others. We can change Samantha’s age and add her current city without changing her name. If you were to use .set instead of .update, Samantha’s name would be deleted, and only the new information provided (age and city) would remain. This is the expected behavior.

Now let’s take a look at their example of updating multiple locations:

Screenshot from the Firebase Blog https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html

Here we see a new technique to update data in multiple locations. It is sets a variable to an empty object, then adds to that object each node path that should be “updated” (these nodes will actually be SET to the values you assign). Then it takes the database root reference, calls .update on it and all the nodes are SET to the assigned values. This is where it got confusing to me. From what I’ve described, this method should probably be called .set.

I would expect there to be two methods that you use to change multiple locations at once. A .set method that would be used for creating new data, and an .update method that could be used for updating existing data.

Let me provide an example:

Let’s say we have a photo sharing app and when we save a photo we want to add it to a /photos node and a /userPhotos node.

When we create a photo it might look something like this:

var userId = 1234
var photos = firebase.database().ref(‘photos/’);
var newPhotoKey = this.photos.push().key
var newPhoto = {};
newPhoto[`/photos/${newEventKey}`] = {
url: ‘http://firebasestorage.com/image1,
likes: 0
};
newPhoto[`/userPhotos/${userId}/${newPhotoKey}`] = {
url: ‘http://firebasestorage.com/image1,
likes: 0
};
firebase.database().ref().update(newPhoto);

Which creates:

{
“photos”: {
“4sdfg486ds4g: {
“url”: “http://firebasestorage.com/image1”,
“likes”: 0}
},
“userPhotos”: {
“1234”: {
“4sdfg486ds4g: {
“url”: “http://firebasestorage.com/image1”,
“likes”: 0}
}
}
}

This works great. Now what if we want to update how many likes the photo has?

This is how I thought it should work based on what I read and that the method is called .update:

var userId = 1234
var photoKey = 4sdfg486ds4g
var updatePhoto = {};
updatePhoto[`/photos/${photoKey}`] = { likes: 1 }
updatePhoto[`/userPhotos/${userId}/${photoKey}`] = { likes: 1 }
firebase.database().ref().update(newPhoto);

However that creates:

{
“photos”: {
“4sdfg486ds4g: {“likes”: 1}
},
“userPhotos”: {
“1234”: {
“4sdfg486ds4g: {“likes”: 1}
}
}
}

The photo URLs are gone! You cannot pass an object to a node and only update the specified children as you can with a single location .update. If we want to update the likes and leave the URLs, the correct usage would be to specify the exact child you want to target in the path:

var userId = 1234
var photoKey = 4sdfg486ds4g
var updatePhoto = {};
updatePhoto[`/photos/${photoKey}/likes`] = 1
updatePhoto[`/userPhotos/${userId}/${photoKey}/likes`] = 1
firebase.database().ref().update(updatePhoto);

Which produces the desired:

{
“photos”: {
“4sdfg486ds4g: {
“url”: “http://firebasestorage.com/image1”,
“likes”: 1}
},
“userPhotos”: {
“1234”: {
“4sdfg486ds4g: {
“url”: “http://firebasestorage.com/image1”,
“likes”: 1}
}
}
}

Since Firebase is seems to be geared toward making things easier for newcomers and non-developers to get an app up and running, I think they should look into renaming the .update function when used with updating multiple locations, or at least providing examples in their docs about how .update on a single node differs from .update on multiple paths.

Here are the resources that helped me figure all this out:

Firebase Blog Post on multi-location updates — Also has some good stuff on deep path queries.

Firebase Docs on creating data structure — Good intro to the idea of denormalization, could use more code examples.

Github Issue on AngularFire2 — David East clarifies the expected behavior for the multi-location update method.

Blog Post on Transitioning from SQL to Firebase NoSQL Databases by Jorge Vergara — This was a guest post on another blog, but I highly recommend his blog and book if you’re looking at implementing Firebase or AngularFire2 with your Ionic 2 app.