Firebase Cloud Functions, (or running code on Firebase Servers!)
Taken straight from the official website:
Firebase Cloud Functions is a hosted, private, and scalable Node.js environment where you can run JavaScript code. Firebase SDK for Cloud Functions integrates the Firebase platform by letting you write code that responds to events and invokes functionality exposed by other Firebase features.
So, what exactly are Firebase Cloud Functions?
They are a hosted, private, and scalable Node.js environment where you can run JavaScript code. In the past, if you needed to do things like creating a user account based on changes in the database, or send push notifications you had to create your server, most people went for a nodejs app on Heroku, and it was working pretty good for them.
The downside of it is that you probably chose a Backend as a Service because you wanted to focus on writing your app, instead of focusing on maintaining servers, handling security, uptime, scalability, and more.
That’s why Firebase created Cloud Functions, now they’re running the server for you, all you need to do is write the JavaScript function, and they’ll take care of setting up the server, scalability, uptime, security, etc.
For a mobile developer, this is great, and it means that you no longer have to deploy your server, you only deploy your functions and are done.
Another AWESOME thing about it, the Firebase team created an SDK for cloud functions, which means that you don’t have to write custom code to connect to your database, they provide the triggers, it currently supports:
- Real-time Database Triggers => Execute functions on database changes.
- Firestore Triggers => Execute functions on Firestore changes.
- Firebase Authentication Triggers => Run functions on user creation, update, or delete.
- Firebase Analytics Triggers => Run functions when analytic goals trigger.
- Cloud Storage Triggers => Want to resize images when they’re uploaded to Firebase Storage? No problemo.
- HTTP Triggers => You get a basic get/post URL where you can call it and run a function.
How does it work?
After you write and deploy a Cloud Function, Google’s servers begin to manage the function immediately, listening for events and running the function when it is triggered. As the load increases or decreases, Google responds by rapidly scaling the number of virtual server instances needed to run your function.
The basic lifecycle of a Cloud Function is:
- The developer writes code for a new Cloud Function, selecting an event provider (such as Real-time Database), and defining the conditions under which the Cloud Function should execute.
- The developer deploys the Cloud Function, and Firebase connects it to the selected event provider.
- When the event provider generates an event that matches the Cloud Function’s conditions, the code is invoked.
- If the Cloud Function is busy handling many events, Google creates more instances to handle work faster. If the Cloud Function is idle, instances are cleaned up.
- When the developer updates the Cloud Function by deploying updated code, all instances of the old version are cleaned up and replaced by new instances.
- When a developer deletes the Cloud Function, all instances are cleaned up, and the connection between the Cloud Function and the event provider is removed.
Cloud Functions in Action
We’re going to go through the entire work-flow of creating and deploying a cloud function for an app, imagine you have an app for Fitness trainers, where they need to create accounts for they clients to help them get shredded (Disclaimer An app can’t help you get shredded, you have to actually lift the weights).
So, whenever the coach adds someone to the database as a client, we’ll have a cloud function listening on that node and create an account for that person.
To start writing Cloud Functions (by the way, let’s call them CF from now on, that way we avoid all the extra typing) we need to install the Firebase CLI, go ahead and open your terminal and type
npm install -g firebase-tools
Depending on your operating system (mostly if you use Linux) you might have to add sudo
before running that line of
code.
Now, we need to login into our Firebase CLI, so that we have access to our apps, for that we need to run:
firebase login
Since our Firebase account is connected to our Google account, that command will open a browser window for us to login to our Gmail/Gsuite account and authorize Firebase. Once you do that, the CLI will log in, and you’ll be able to access its full power.
Now, I’m going to assume you’re into your app folder in the terminal, because you know, you’re working on a fitness app, first, get out of your app’s folder, and then create a new folder for your CF:
cd ..
mkdir functions
cd functions
That will leave you with 2 folders, one for the app and one for functions, both folders are at the same level (tho they
don’t have to, they can be in different folders if you want) and then it will take you inside the functions
folder,
where you’ll initialize your functions:
firebase init functions
That command will show a list of the applications that exist in the Firebase Console, and you need to choose the one we’re working on so that the initialization connects the firebase admin to that specific application.
It also asks you if you want to use JavaScript or TypeScript, I’d go with TypeScript.
When you choose an application, then it creates an entire folder structure for you inside the functions
folder we
created:
A few things to keep in mind:
- It is a node.js environment, that means you can run
npm install --save package_name
and use whatever package you want in your functions. - All your functions need to be created and exported inside
index.ts
- It comes with 2 packages installed and imported:
import { functions } from 'firebase-functions';
import { admin } from 'firebase-admin';
admin.initializeApp();
firebase-functions
refers to the CF SDK for Firebase, where we have built-in functions to listen to authentication or
database changes.
And the firebase-admin
package is the ADMIN SDK for Firebase, so we can write, read from the database, create users,
and more.
Pay special attention to this line:
admin.initializeApp();
If you’ve been paying attention before, the .initializeApp()
function initializes our app, same as when we use the JS
SDK or AF2 in our apps.
Now it’s time to write our first function (it was exciting to run this and see the changes in the database happen
without me writing to it), we’re going to create a function called createClientAccount()
.
The function will listen to the database, to access the database from CF we have the functions.database
functions,
we’ll have it listen to a specific node:
exports.createClientAccount = functions.database.ref('/userProfile/{userId}/clientList/{clientId}');
Right there we created a reference to the database in the node:
'/userProfile/{userId}/clientList/{clientId}';
One important thing to keep in mind, the database references inside CF accept wild cards, so inside the reference,
{userId}
and {clientId}
are wild cards, meaning that:
'/userProfile/jsmobiledev/clientList/client1';
'/userProfile/jsmobiledev/clientList/client2';
'/userProfile/another_weird_id/clientList/client54';
'/userProfile/another_weird_id/clientList/client49';
All of those will match that database reference, and inside the function, we’ll later be able to grab those values.
Now we need to trigger our function, and this might seem familiar to you, you know we have several triggers for database
listeners in our apps, especially things like .on()
or .once()
, for CF we also have triggers to listen to the
database, we have one called .onWrite()
that will trigger when someone writes to that database node. So our function
then will look like this:
exports.createClientAccount = functions.database
.ref('/userProfile/{userId}/clientList/{clientId}')
.onWrite((change, context) => {
// We'll handle all the logic here
});
That’s an active function, ready to listen to that database node and run every time someone writes data there (it will
also trigger on updates or deletes to that node, since deleting the node is doing .set(null)
).
The first thing we need to do is to create the new user, for that, we’re going to use the firebase admin, one quick tip,
so you wrap your head about the admin SDK, is the same thing as the JS SDK, you just preface everything with admin.
instead of firebase.
, so if in the regular JS SDK we do:
firebase.auth().createUser(credentials);
In the admin SDK we’ll do:
admin.auth().createUser(credentials);
So, go ahead and create the new user account:
exports.createClientAccount = functions.database
.ref('/userProfile/{userId}/clientList/{clientId}')
.onWrite((change, context) => {
return admin
.auth()
.createUser({})
.catch(error => {
console.log('Error creating new user:', error);
});
});
We want to pass some information to that createUser()
function, you don’t need anything, but if you send it blank it
will create an anonymous account, and then it will be a pain to link it to our user’s account.
We’re going to pass several values, we want:
- The UID, we’re not aiming to get one of Firebase automatic UIDs (user ID), we’re passing our UID, the one that Firebase creates as an ID inside the database node:
'/userProfile/{userId}/clientList/{clientId}';
So we’re taking that clientId
and passing it to the function as the uid, we’re also passing the client’s email as the
username/email for authentication, we’re passing the client’s name to the user object, and we’re setting up a temporary
password.
You might be wondering, but Jorge, where the heck are we getting the email and name from? and I have great news, the
CF SDK can access all of the information inside the '/userProfile/{userId}/clientList/{clientId}'
node, it’s available
in the event
variable that the onWrite()
function returns, so our function will look like this:
exports.createClientAccount = functions.database
.ref('/userProfile/{userId}/clientList/{clientId}')
.onWrite((change, context) => {
return admin
.auth()
.createUser({
uid: context.params.clientId,
email: change.after.val().email,
password: '123456789',
displayName: change.after.val().fullName,
})
.catch(error => {
console.log('Error creating new user:', error);
});
});
Notice how we’re accessing the data in 2 different ways, we get access to the wild cards through the context.params
interface, so at any point, we can do context.params.clientId
and we get the current client’s ID to set as the new
user’s uid.
And the data inside the object is available via the change.after
interface, so we have access to the email address,
the full name, and even the starting weight through change.after.val().property_name
.
And that’s it, all we have to do now is to deploy our function to Firebase servers and BOOM, every time a coach creates a new client record in the database, that CF will trigger.
Before deploying it, I want to make sure I go over a few tips to make your life easier, and to ensure they work.
Cloud Functions can be killed
Yup, they can be killed without completing, server can kill idle functions, to avoid that we need to make sure we’re returning a promise, when you return a JavaScript Promise to a CF, that function keeps running until the promise is resolved or rejected, that way CF avoids killing your function until it either completes successfully or crashes with an error.
Avoid infinite loops at all costs
There’s something you need to pay special attention to, running an infinite loop, what would have happened (it did) if
I would have decided after creating the user, to store some other information about the user inside the coaches
clientList
node?
It would have triggered the function again, the result of the function would invoke the call of the function, and it would run in an infinite loop.
It was the reason why I added a custom uid
to the user creating instead of letting Firebase create it. My original
idea was to create the user and then write that user’s uid
to replace the client’s ID in the Coaches profile node. If
I would have followed that path, then when the function changes the ID in the coaches node it would have triggered
itself again.
Deploying your CF to Firebase
Now comes the fun part, we’re going to deploy our function to Firebase servers so it can work, this part is easy, all you need to do is open up your terminal, remember, you need to be inside the folder where you created the functions
firebase deploy --only functions
Then pay attention to your terminal, it will let you know if the function was successfully uploaded or if it crashed because there was a syntax error.
So, what do you think about Cloud Functions for Firebase? Is it something you’ll be using? Do let me know!