Learning Firebase: Creating a blog article object — Making a blog pt. 2

Toni-Jan Keith Monserrat
6 min readOct 2, 2016

--

This is part 2 of the article series on Learning Firebase. Part 1 is here and Part 1.5 is here.

Introduction

I like to apologize for this long overdue article. :( I had a lot of activities in the past week that I couldn’t squeeze in this one. But now I am writing this while mentoring at the GDG Philippines Dev Fest 2016, so yey free time!

Now that’s done, let’s start.

Last part (and a short advanced PS note here) talks about structuring your Firebase to create an efficient filtering system, taking advantage of the unique querying system and security rules of Firebase.

What we will be doing is to build upon what we have learned and create a simple way to create blog articles.

Review

Before we start, let’s review the needed structure…

Data structure for the hypothetical blog

Details are:

  • the User has a role: “admin”, “editor”, “contributor”, or “member”
  • a “member” can only create / edit / delete their Comment.
  • a “contributor” can do any functionalities the “member” can, and also create / edit / delete their own Article.
  • an “editor” can do any functionalities the “member” and “contributor” can, and can set any Article to be published and be seen by all. They can also give promote any “member” into a “contributor” as well.
  • an “admin” can do all of the above, and also can promote any “member”, “contributor”, or “editor” into an “editor” or “admin”.
  • An Article can only have one author (as of the moment, more on this later), but has many Comments.
  • An Article will only be viewed by all if it is published (published == true), or will only be viewed by the “admin”, “editor” or the owner if it is not published.
  • An Article can be found using the slug_name, which is the “slugified” title or the Article plus random characters for uniqueness.

And our current Firebase structure…

{
"article_group" : {
"article" : {
"article1" : {
"body" : "This is the body of the article",
"date_edited" : 1333065600000,
"slug_name" : "this-is-an-article-abcdef",
"title" : "This is an Article",
"uid" : "user1"
},
"article2" : {
"body" : "This is a new body",
"date_edited" : 1333065600000,
"slug_name" : "article-on-firebase-hijklm",
"title" : "Article on Firebase",
"uid" : "user2"
},
"article3" : {
"body" : "This is a new body",
"date_edited" : 1333065600000,
"slug_name" : "article-on-firebase-hijklm",
"title" : "Article on Firebase",
"uid" : "user2"
}
},
"article_list" : {
"article1" : {
"published" : 1459296000000
},
"article2" : {
"draft" : 1333065600000
},
"article3" : {
"published" : 1461974400000
}
}
}
}

And current Firebase rules…

// Firebase Rules snippet"article_group": {
"article": {
"$article": {
".read": "data.parent().parent().child('article_list')
.child($article).child('published').exists()"
},
},
"article_list": {
".read": true,
}
}

Now with that done…

Creating a blog — the shell

Let’s create a simple editor.

<div id="content-creator">
<label>
Title
</label>
<br/>
<input
type="text"
placeholder="Title"
name="title"
id="title"
style="width: 100%"/>
<br/>
<label>
Body
</label>
<br/>
<textarea
name="body"
id="body"
rows="5"
placeholder="Body Text"
style="width: 100%">
</textarea>
<br/>
<button id="submit-button">
Submit
</button>
</div>

And the script would be…

var submitButton = document.querySelector('#submit-button');
var titleText = document.querySelector('#title');
var bodyText = document.querySelector('#body');
submitButton.addEventListener('click', function(){
var title = titleText.value;
var body = bodyText.value;
console.log(title, body)
});

(You can see an example here, built on top of the Part 1.5 version)

Creating a blog — Saving

Given above, let’s try replacing the submitButton’s click event with this.

submitButton.addEventListener('click', function(){
var title = titleText.value;
var body = bodyText.value;

// Add this here
var articleRef = 'article_group/';
var articleData = {
title: title,
body: body,
date_edited: firebase.database.ServerValue.TIMESTAMP,
uid: 'user1',
slug_name: title.replace(/\s/g, '-')
};
var key = firebase.database().ref(articleRef +
'article').push().key;

var updates = {};
updates[articleRef + 'article/' + key] = articleData;

return firebase
.database()
.ref()
.update(updates)
.then(function(){
alert('Added ' + title);
})
.catch(function(error) {
console.log(error);
alert(error.message)
});
});

You can see the example here.

Now, when you clicked submit, something happens.

Oh no

So it doesn’t allow us to add posts as of now. Why? Because we didn’t put our security rules to write.

So we add this rule here:

// Firebase Rules snippet"article_group": {
"article": {
"$article": {
".read": "data.parent().parent().child('article_list')
.child($article).child('published').exists()"
".write": true
},
},
"article_list": {
".read": true,
}
}

Note: THIS IS VERY DANGEROUS. We just made our database write-able by anyone. Actually, we just made adding, updating and deleting articles be allowed to everyone. Do this only on development when you are still just testing things.

Usually we would write something like this:

".write": "auth != null"

to say that the user must be logged in somehow, but we haven’t gone to client authentication yet so this one will suffice as of now.

Again, this is very dangerous and would not advice it on production deployments. You have been warned :)

Now going back, if you clicked on it… it will show this.

Yey!

But it doesn’t update the list…

Where is the new article? (this is the old list by the way…)

But when we looked at the Firebase database…

It’s there!

So why is that? Remember this code snippet?

firebase
.database()
.ref('article_group/article_list')
.orderByChild('published')
.limitToLast(limit)
.startAt(1)
.on('child_added', function(data) {
// get the data from here
})

We are not actually reading in the ‘/article_group/article’ reference but the ‘/article_group/article_list’. From there, we get the data inside using the keys…

firebase
.database()
.ref('article_group/article/' + data.key)
.on('value', function(articleData) {
// get the data from here
})

So the new data is not being added because we are not updating ‘/article_group/article_list’.

Usually, what we do is to do sequentially:

  • We create the data object
  • Then we add it to article_list

It would be coded this way (using set instead of update):

var key = firebase.database().ref(articleRef + 
'article').push().key;

return firebase
.database()
.ref('article_group/article/' + key)
.set(articleData)
.then(function(){

// then add it to the list
return firebase
.database()
.ref('article_group/article_list/' + key)
.set({
published: firebase.database().ServerValue.TIMESTAMP
})
})
.catch(function(error) {
console.log(error);
alert(error.message)
});

But that’s too tedious. One problem with this is that there will be a possibility of the article being created but the list having an error. That means the article being saved but we have no way of knowing that it exists already because it was not saved in the ‘article_group/article_list’.

So the better way to make this transaction atomic is using the update function (that’s why we used it already because now we can build on top of it). Just add this in the code after the ‘var updates = {}’ and before ‘return firebase…’:

var updates = {};
updates[articleRef + 'article/' + key] = articleData;
// add this code here
updates[articleRef + 'article_list/' + key] = {
published: firebase.database.ServerValue.TIMESTAMP
}
return firebase...

And don’t forget to change your rules…

// Firebase Rules snippet"article_group": {
"article": {
"$article": {
".read": "data.parent().parent().child('article_list')
.child($article).child('published').exists()"
".write": true
},
},
"article_list": {
".read": true,
"$article": {
".write": true
},
}
}

And now it works like a charm. You can see an example here (it will show an error still because I turned off the capability to write on my database, so you need to use yours, add the write security you want to use, and it will work.)

Now what happens here is that updating both ‘article_group/article’ and ‘article_group/article_list’ is an atomic transaction. It means all updates must work or nothing will work at all.

Final Thoughts

We now have the capability to create data documents “atomically”. Next week, we will talk about adding authentication capabilities to secure our blog.

If you have questions, ask away on the comments.

--

--

Toni-Jan Keith Monserrat

Google Developer Expert on Web Performance, Web Components & Firebase, Husband to a Painter/Artist/Chef, Father to two princesses, HCI Researcher, and Gamer.