From Idea To Reality in Under 50 Minutes (Mostly) With Angular And Firebase Part III - Firebase Cloud Functions
This is the third part of the super series on Angular and Firebase. If you missed any of the first or second parts then I encourage you to take a look!
Hi! Hello! Hola! Gooood day! Welcome back to the Angular and Firebase series that helps you bring your most cherished and coolest ideas to life!
In the last part of the series we built baby-gotchi, a web application that helps you become a better dad by taking care of a fictional baby:
We used the super duper awesome Angular cli to bootstrap our app, scaffold our components, services and pipes, and to build our app ready for production. We also took advantage of Firebase Realtime database and AngularFire2 to store and sync our data across all of our clients. Finally we used Firebase Hosting and the Firebase cli to deploy our app into the cloud and made it available for the world to use.
In baby-gotchi, a parent can give birth to a child and then take care of her: feed her when She’s hungry, cuddle her when She’s in need of some love, etc. You can also play God, control the baby and make her feel hungry or sleepy. But it’d be much cooler and more reflective of reality (and it’d be better for your soul) if the status of the baby changed over time by itself. That is, as time passed, the baby would be feeling hungrier, more sleepy, more in need of cuddles on her own terms, just like a regular baby.
So that’s what we are going to do! In this part of the series, we’ll learn how to use Firebase Cloud Functions to implement that functionality, a baby lifecycle that will update all of the baby status indicators: sleepiness, shittiness, hunger and life as time goes by.
Let’s get started!
Programming Life
Life… What is life? What distinguishes a human from a rock? Is it some aethereal force inbued into us by the God Prometheus? Is it what happens when you are busy making other plans? Is it to crush your enemies — See them driven before you, and to hear the lamentation of their women!?
One of the beauties of programming is that you get to be a deity in your own little piece of universe and can define stuff like what life is.
In our baby-gotchi universe, life will consist of a very simple program that will update the baby status every minute following these rules below:
- Increase baby hunger by
1
up to a maximum of100
- Increase baby sleepiness by
1
up to a maximum of100
- Increase baby shittiness by
1
up to a maximum of100
- For the life indicator, if the average of hunger, sleepiness and shittiness is:
<=25
we increase life by10
>25
and<=50
we increase life by1
>50
and<=75
we decrease life by1
>75
and<=90
we decrease life by1
>90
and<100
we decrease life by10
- equal to
100
we decrease life by50
Boom! And that’s life! In summary, we increase the hunger, sleepiness and shittiness indicators linearly over time and based on the average of these indicators we either increase or decrease life. The higher or smaller the average the higher or smaller the impact on the life indicator. That is, if the baby has been neglected for a while, She’ll lose her life at a faster pace.
So now that we have an idea about how our baby lifecycle will work, we need to do the actual implementation. In the past, that would’ve typically involved writing a web application backend of some sort using a popular framework like ASP.NET MVC or Express and hosting it in a server somewhere. But that sounds like an awful lot of work, wouldn’t it be awesome if I could just write this logic and have it run by itself?
It would! And that’s where serverless computing (the latest and greatest of tech buzzwords) comes into play. Serverless computing is, essentially, like having Functions as a Service: you grab your logic, put it inside a function, configure a trigger, deploy it and voilá, you’re ready to use it.
Firebase Cloud Functions
Firebase Cloud Functions is serverless computing integrated with all the features within Firebase. It gives you the possibility to write arbitrary logic and have it be triggered by and interact with Firebase (Realtime Database, Analytics, Storage, etc) and with the external world through HTTPS.
In a nutshell:
Getting Started with Firebase Cloud Functions
Getting started with Firebase Cloud Functions is super straightforward. If you’ve followed along during this series, you already have all you need, namely, the Firebase cli.
In the last part of this series we used the Firebase cli to deploy baby-gotchi into Firebase hosting. We can take advantage of the same cli to setup our Cloud Functions and deploy them to Firebase.
First comes the setup. Type the following in your terminal within your app root folder:
PS> firebase init functions
This will trigger a wizard that will guide you through the setup necessary to start using Firebase functions. When it is complete, the cli will have updated your firebase configuration file firebase.json
and will have created a functions
folder where to store your Firebase functions with a single index.js
file.
If you open the index.js
file you’ll find the following:
const functions = require('firebase-functions')
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// response.send("Hello from Firebase!");
// });
Let’s uncomment the helloWorld
function and deploy it to Firebase to get our feet wet with Cloud Functions right away.
We uncomment the function like this:
const functions = require('firebase-functions')
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// call this function on HTTPS request
exports.helloWorld = functions.https.onRequest((request, response) => {
// respond with "Hello from Firebase"
response.send('Hello from Firebase!')
})
Then save it and deploy it by running the following command:
PS> firebase deploy --only functions
This command will deploy all the cloud functions that you have defined within your project. The terminal should show the following:
=== Deploying to 'baby-gotchi'...
i deploying functions
i functions: ensuring necessary APIs are enabled...
i runtimeconfig: ensuring necessary APIs are enabled...
✔ runtimeconfig: all necessary APIs are enabled
✔ functions: all necessary APIs are enabled
i functions: preparing functions directory for uploading...
i functions: packaged functions (862 B) for uploading
✔ functions: functions folder uploaded successfully
i starting release process (may take several minutes)...
i functions: creating function helloWorld...
✔ functions[helloWorld]: Successful create operation.
✔ functions: all functions deployed successfully!
✔ Deploy complete!
Project Console: https://console.firebase.google.com/project/baby-gotchi/overview
Function URL (helloWorld): https://us-central1-baby-gotchi.cloudfunctions.net/helloWorld
When the deployment is finished you’ll be able to call your cloud function using the url that’s been assigned to it. This bit:
Function URL (helloWorld): https://us-central1-baby-gotchi.cloudfunctions.net/helloWorld
from the snippet above.
You can use curl
to test the function. If you do, you’ll see the following:
Note that if you don’t have curl
in your machine you can just type the url in your browser and you’ll get the same result
PS> curl https://us-central1-baby-gotchi.cloudfunctions.net/helloWorld
Hello from Firebase!
Awesome! You have deployed your first Cloud Function to Firebase! Congrats!
Implementing The babyCycle
Firebase Cloud Function
We’ve completed the Hello World version of Firebase Cloud Functions but we’re going to need to go a little bit further to implement our own version of life.
Let’s create a new function within the index.js
file that we’ll call babyCycle
:
const functions = require('firebase-functions')
exports.babyCycle = functions.https.onRequest((request, response) => {
// TODO: baby lifecycle implementation
})
This function needs to go through all of the babies we have stored in Firebase Realtime database and update their status within a transaction.
We begin by importing the firebase-admin
module that will give us access to the Realtime database and starting a transaction for our /babies
:
const functions = require('firebase-functions')
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
exports.babyCycle = functions.https.onRequest((request, response) => {
admin
.database()
.ref('/babies')
.transaction(babies => {
// Update babies status
})
.then(() => {
// Success
})
.catch(error => {
// Failure
})
})
The transaction will either succeed or fail, at which point we will need to send a response back to the client.
The value stored within /babies
is an object with key/value pairs of generated keys and babies respectively. We will traverse all the properties (keys) within that object and we’ll update all the baby statuses using the logic we specified earlier in this article:
const functions = require('firebase-functions')
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
exports.babyCycle = functions.https.onRequest((request, response) => {
// TODO: improve to have a transaction per baby
// now we have an unnecessary transaction for all babies... Doh!
admin
.database()
.ref('/babies')
.transaction(babies => {
console.log('babies to update: ', babies)
if (babies) {
for (baby in babies) {
console.log('baby to update', baby)
updateBabyStats(babies[baby])
}
console.log('babies updated: ', babies)
}
return babies
})
.then(() => {
console.log('One baby tick completed')
response.send('One baby tick completed')
})
.catch(error => {
console.error('Error in babyCycle cloud function: ', error)
response.send(500, 'ouch, Houston we have a problem')
})
})
function updateBabyStats(baby) {
if (baby.shittiness < 100) baby.shittiness += 1
if (baby.sleepiness < 100) baby.sleepiness += 1
if (baby.hunger < 100) baby.hunger += 1
if (babyStatusAvg(baby) <= 25 && baby.life < 100) baby.life += 10
else if (babyStatusAvg(baby) <= 50 && baby.life < 100) baby.life += 1
else if (babyStatusAvg(baby) == 100 && baby.life > 0) baby.life -= 50
else if (babyStatusAvg(baby) > 90 && baby.life > 0) baby.life -= 10
else if (babyStatusAvg(baby) > 75 && baby.life > 0) baby.life -= 1
if (baby.life > 100) baby.life = 100
if (baby.life < 0) baby.life = 0
}
function avg(...items) {
var sum = items.reduce((a, b) => a + b)
return sum / items.length
}
function babyStatusAvg(baby) {
return avg(baby.shittiness, baby.sleepiness, baby.hunger)
}
If the transaction succeeds we return a HTTP 200
Ok and if something goes wrong we’ll return a HTTP 500
internal server error:
exports.babyCycle = functions.https.onRequest((request, response) => {
// TODO: improve to have a transaction per baby
// now we have an unnecessary transaction for all babies... Doh!
admin
.database()
.ref('/babies')
.transaction(babies => {
// logic
})
.then(() => {
console.log('One baby tick completed')
response.send('One baby tick completed')
})
.catch(error => {
console.error('Error in babyCycle cloud function: ', error)
response.send(500, 'ouch, Houston we have a problem')
})
})
And that’s it. We can now deploy our babyCycle
function to Firebase using the Firebase cli. Again, use your terminal to type:
PS> firebase deploy --only functions:babyCycle
This addition of functions:functionName
will let you deploy only the function that you specify: In this case babyCycle
.
When the function has been successfuly deployed, you can test it by using curl
or typing it within your browser. As a result, you should see the following:
PS> curl https://us-central1-baby-gotchi.cloudfunctions.net/babyCycle
One baby tick completed
To make a more lively test you can try opening one window with your baby-gotchi app, going to the Baby Care view and then triggering the babyCycle
function. You should see how everytime you call the function the baby status is updated. Awesome!
Triggering the Baby Cycle Every Minute
There’s one final step that we need to take in order to complete our baby lifecycle implementation. Somehow, we need to be able to trigger the babyCycle
function every minute. Unfortunately, Firebase Cloud Functions doesn’t offer any type of scheduling just yet.
There’s alternative solutions like this one from the Firebase Blog that works in conjunction with Google App Engine but, in this case, I don’t want to have to deploy an additional web app. Instead I will use cron-jobs.org that is a very simple cron-job-as-a-service kind of thing which allows you to setup cron jobs in a web UI. Just create a free account, a new cron job, specify the URL that you want to ping and the interval and you’re done. Hopefully, in the future, we’ll have the same kind of functionality within the Firebase UI.
Want to Learn More About Firebase Cloud Functions?
In this article we’ve only scratched the surface on what Firebase Cloud Functions have to offer. If you want to learn more, I recommend you to take a look at these Firecasts on Cloud Functions, a series of web casts by the Firebase team focused solely on cloud functions. Enjoy!
Up Next: Providing an Awesome Mobile Experience with Progressive Web Apps
Great job! Hope that you’ve enjoyed learning about Firebase Cloud Functions and how hassle free they are to get some arbitrary logic up and running up in the cloud in concert with all of the other Firebase services.
Our MVP is feature complete as one might say. We’re ready to go to release our app into the hands of our users, ready to validate the usefulness of our idea. But before we push the button we’re going to do one last thing. Have you heard about Progressive Web Apps? Well the next and last article in this series is all about PWAs.
PWA meet baby-gotchi, baby-gotchi meet PWA and the path to providing awesome mobile web experiences.
Written by Jaime González García , dad, husband, software engineer, ux designer, amateur pixel artist, tinkerer and master of the arcane arts. You can also find him on Twitter jabbering about random stuff.