ABEJA Tech Blog

株式会社ABEJAのエンジニアたちがAI, IoT, Big Dataなど様々なテーマについて発信します

Firebase on Vue

On the top of ABEJA’s smart AI brain, Abeja also comes with a ‘pretty face’, and we, the SaaS development division team, is the one who’s responsible for it. We love embracing new and exciting technologies, and our technology stack proves it:

f:id:abeja:20170726184558p:plain

In the last couple of weeks, we’ve been working on a new internal project, this time around, instead of AWS Lambda + Vue.js combo, something we’ve been using for a long time, we switched to Firebase and Cloud Functions for Firebase as our backend solution. We had so much fun working with it, and we’d like to spread the love, share with you our experiences in this post, here’s what we gonna go about it.

  • Why Firebase
  • Firebase as the database
    • Real-time
    • Authentication
    • Deploy and hosting
    • Vue.js and vuefire
  • Firebase with Cloud Functions
    • The good
    • The not so good
  • Lesson learned
    • Best practice of structuring your data
    • Aggregations
  • Conclusion

You could probably find tons of articles online talking about the good or bad of either Firebase or Vue.js, as our fellow programmers always say, talk is cheap, show me the code, so, we’d like to take a different approach, not only we’ll be sharing the code, but also, to present you an actual working application that you can play around with. Now, we present you, the Abeja totalizer, a real-time voting application built with Firebase, Cloud Function for Firebase, and Vue.js. With this simple web application, you will be able to create a questionnaire, share it with your friends, and track the results in real-time, it’s a simple web application, and it perfectly demonstrate some of the awesome features this technology stack offers, which we will be getting into next.

Why Firebase

Well, the reason is simple, we’re a front-end focus team, and we don’t want to maintain a dedicated backend stack, we were looking for a BaaS solution for our small to medium size projects, looking at the most advanced BaaS solution out there, it comes down to Firebase and Graphcool, and since we were thinking about more Google Cloud Platform and less Amazon AWS, then Firebase became the most obvious choice.

Firebase as the database

Real-time

Real-time. As a front-end focus team, building a real-time application sounds a bit intimidating, it’s not easy to pull it off without a sophisticated backend solution. Firebase comes with real-time by default, you will not even notice the difference while developing your app, it just works.

Authentication

Authentication. again, this is one of those things that every non-trivial app will need, but will take some time to do it right. Firebase provides a solid solution that you can easily integrate with your front-end stack, email and password based authentication, federated identity provider (Google, Facebook, Twitter, Github) integration, phone number authentication, or even anonymous authentication, you name it, it has it.

Deploy and hosting

Hosting, your shining new web app is ready to roll, where to host it? CDN? SSL? They’re as important as the features your app offer. And they’re all taken good care by Firebase’s production-grade hosting solution. As for deployment, here’s our deployment script, it’s pretty straight forward, after JavaScript gets transpiled, firebase deploy that’s all you need to do, then your app is ready to be shared with millions of users :)

// abeja-totalizer/package.json
"scripts": {
  "prepackage-functions": "rimraf functions",
  "package-functions": "babel 'functionsES6' --out-dir 'functions' --presets=es2015 --copy-files --ignore 'node_modules'",
  "postpackage-functions": "cd functions && yarn",
  "deploy": "yarn run package-functions && firebase deploy"
}

If you’re not a front-end savvy developer, you may wondering what the hell with prepackage, package and postpackage, well, long story short, we’re basically using the babel to transpile our ES6 JavaScript code down to ES5 cause currently Google Cloud Functions supports only Node.js version 6.11, we know the versioning is a bit confusing, for more information on this, as well as how to write Cloud Functions with ES6, here’s a good article to get started. Link

Vue.js and vuefire

So now we have a solid database, how about connecting it to our front-end stack? Which is Vue.js in our case, we don’t plan to get too much into Vue.js as its popularity speaks for itself. All we’ll say is that Vue.js allow us to build componentized web applications blazing fast! Back to connect Firebase to Vue.js, vuefire is the library we rely upon to make it happen, and on the top of vuefire, we also wrote a plugin, which makes getting data from Firebase to your Vue component even easier.

Here’s our plugin implementation:

// abeja-totalizer/src/libs/FirebaseDBPlugin.js
import firebase from '../helper/firebaseDB'

const db = firebase.firebaseApp.database()
const DATABASE_NAME = process.env.DB_NAME
const fbRef = ref => {
  return db.ref(`${DATABASE_NAME}/${ref}`)
}
const questions = db.ref(`${DATABASE_NAME}/questions`)
const answers = db.ref(`${DATABASE_NAME}/answers`)
const FB_MAPPING = {
  'questions': function () {
    this.$bindAsArray('questions', questions)
  },
  'question': function (param) {
    if (!param.questionKey) return
    this.$bindAsObject('question', questions.child(param.questionKey))
  },
  'answers': function (param) {
    if (!param.questionKey) return
    this.$bindAsArray('answers', answers.child(param.questionKey))
   },
   'myAnswer': function (param) {
    if (!param.questionKey) return
    if (!param.myId) return
    this.$bindAsObject('myAnswer', answers.child(param.questionKey).child(param.myId))
  }
}
    
const fbBinding = function () {
  let fbBind = this.$options['fbBind']
  if (typeof fbBind === 'function') fbBind = fbBind.call(this)
  if (!fbBind) return
  Object.keys(fbBind).forEach(k => {
    const b = FB_MAPPING[k]
    b.call(this, fbBind[k])
  })
}
const init = function () {
  fbBinding.call(this)
}
    
const install = (Vue) => {
  Vue.prototype.$fbRef = fbRef
  Vue.prototype.$fbBinding = fbBinding
  Vue.mixin({
    created: init // 1.x and 2.x
  })
}
    
export default {
  install
}

You can use this as any other Vue.js plugins:

// abeja-totalizer/src/main.js
import FirebaseDBPlugin from './libs/FirebaseDBPlugin'
Vue.use(FirebaseDBPlugin)

Then in your Vue.js component, you can simply drop whichever firebase data object you’d like:

// abeja-totalizer/src/components/Main.vue
export default {
  fbBind: function () {
   return {
      'questions': {}
    }
  }
}

Firebase with Cloud Functions

Cloud Functions for Firebase, the good

Cloud Functions for Firebase. This is THE most exciting feature Firebase added not long ago. Firebase was never considered a one-stop BaaS solution cause having a real-time database is not enough. Everything changed with the arrival of Cloud Functions for Firebase, now you’ll be able to write your business logic, which could be triggered by either HTTP request or real-time Firebase change event (and much more), how cool is that! And thanks for the serverless architecture, you write your function, deploy it, then you don’t ever need to worry about it ever again, no maintenance, no scaling concerns, no worry be happy :D

Cloud Functions for Firebase, the not so good

Well, nothing is perfect, at the time of writing this post, Cloud Functions for Firebase has one downside as far as we can tell, you can’t really debug your non-HTTP triggered function in local development environment, Firebase command line tool has this experimental feature which allows you to debug HTTP triggered functions, and also Google does provide a tool called Cloud Functions Local Emulator for debugging, however, it only works with Cloud Functions, not Cloud Functions for Firebase, Google Cloud Functions and Cloud Functions for Firebase are kind of the same thing but with some slight differences. So, is there any alternatives besides deploy, run, stare at the logs? (by the way, Google Cloud Platform logging feature is very nice). The answer is unit tests, take a look at this (https://firebase.google.com/docs/functions/unit-testing) article for more information.

Lesson learned

Besides the tips on debugging, there are some other lesson-learned best practices when working with Firebase we’d like to share.

Best practice of structuring your data

Keep your database structure as flat as possible. It’s very attempting to nest one property within another one, nested properties have two major problems, almost impossible to query for one, and another one is too large of payload for a single request. And the solution would be flattening your data as always, here’s an example of how we structured the data for one of our internal projects, everything is flattened, and we use firebase generated key to form the relationship between data objects.

// customers has many shops, each shop has many devices, devices owned by customer
customers
  -KnrCVAqhTQ33fGkz50s
    shops
      -KnrCVBHeSztSd86_CVI: true
      ...
shops
  -KnrCVBHeSztSd86_CVI
    customerKey: -KnrCVAqhTQ33fGkz50s
    devices
      -KnXFP1dGQoLsu4dzran: true
      ...
devices
  -KnXFP1dGQoLsu4dzran
    customerKey: -KnrCVAqhTQ33fGkz50s
    shopKey: -KnrCVBHeSztSd86_CVI
    name: '

Aggregations

We do miss the powerful aggregation functions provided by the traditional relational databases. Turns out you’re not exactly out of luck, one potential solution could be taking advantage of Cloud Functions for Firebase, create your own aggregation functions, which get triggered upon Firebase data changes, yet another use case for Cloud Function for Firebase 😃. Here’s a naive example of doing this just for reference:

import 'babel-polyfill'
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
admin.initializeApp(functions.config().firebase)
  
const setFirebaseRef = async (ref, index, plusOrMinus) => {
  let countRef = ref.child(`${index}/count`)
  let countValue = (await countRef.once('value')).val()
  countRef.set(countValue + plusOrMinus)
}
    
export const countSelect = functions.database.ref('/totalizer/answers/{questionKey}/{userId}')
  .onWrite(async event => {
    console.log('on write')
    let selectionRef = admin.database().ref(`/totalizer/questions/${event.params.questionKey}/selections`)
    if (event.data.previous.exists()) {
      console.log('update')
      let prevIndex = event.data.previous.val()
      await setFirebaseRef(selectionRef, prevIndex, -1)
      let newIndex = event.data.val()
      await setFirebaseRef(selectionRef, newIndex, 1)
    } else {
      console.log('create')
      let index = event.data.val()
      await setFirebaseRef(selectionRef, index, 1)
    }
  })

The conclusion

After working with it for a couple of weeks, we all agree that Firebase on Vue will be our de-facto technology stack for the front-end team to build future projects. And we highly recommend you to do so if you want to build real-time, robust web applications in less time. Hopefully this blog post, the sample application as well as our code examples have done their job to win you over. 😃

That’s it, you can find rest of the sample codes in this repository https://github.com/abeja-inc/abeja-totalizer , until next time, happy coding.

ABEJA SaaS Dev

We are Hiring!

We are also continuously looking for motivated engineers on various positions on frontend, backend and AI platform. If you want to be a part of fast paced startup based in Tokyo please contact us via email ( recruit@abeja.asia ) or Linkedin page. To see what we are working on, have a look at other posts by our Engineers and Researchers.

To know more about ABEJA and its business: www.wantedly.com