Learn How to Use Firebase Cloud Functions to create a user profile

First Published: 11 May 2017
Updated on: 24 September 2018

A week or so ago (Yes, I get that if you read this later it won’t be a week, but bear with me) a reader asked me an interesting question, if you’ve seen my other authentication tutorials, you’ll see that right after we create or sign up a new user, we create a /userProfile/<userId> node to store the data we have about our user.

That way we create a record in our database and when the user starts adding more info about herself we can just add it to that user profile node.

The question was:

Jorge, but what happens if right before the account creation promise resolves, the app crashes?

You can imagine that, right? You send the credentials to Firebase, Firebase creates the user account, but the app crashed so the app didn’t trigger the function to create the user profile.

I don’t see any way, handling it on the client side, to make sure that runs, apps crash, websites crash, users might think it finished and close the app, etc.

Looking into it I thought, why not let the server handle it?.

And that’s exactly what we’re going to learn about today, we’re going to use Firebase Cloud Functions to create an authentication listener on Firebase servers, that listener will trigger every time a new account is created, and it will then create the user profile for that account.

This gives us 2 major advantages:

  • Firebase servers will make sure, every time a new user account is created, to create the user profile database node for that account.
  • We’re taking work from the client (our app) and sending it to the server, which will make our app more responsive since it has fewer things to do now :-)

With that intro, let’s jump into coding, make sure to grab the code from Github so you can follow along with the tutorial.

Initialize Firebase

The first thing you need to do in your new app is to install Firebase and initialize your Firebase app. We’re building this example using AngularFire2, so we’ll be initializing everything in the app.module.ts file.

So assuming you’ve already created your Ionic app, use the terminal or command line to navigate to the app’s folder, then install Firebase and AngularFire2:

npm install --save firebase angularfire2

Once everything is installed, go into app.module.ts and import the AngularFire2 modules you’ll need, then initialize the application:

...
...
import { AngularFireModule } from 'angularfire2';
import { AngularFireAuthModule } from 'angularfire2/auth';

export const firebaseCredential = {
  apiKey: "AIzaSyBwEUe6x_w_yLFrr--xYLQJLxRT2Rc8vtY",
  authDomain: "ionic-firebase-auth-9f555.firebaseapp.com",
  databaseURL: "https://ionic-firebase-auth-9f555.firebaseio.com",
  projectId: "ionic-firebase-auth-9f555",
  storageBucket: "ionic-firebase-auth-9f555.appspot.com",
  messagingSenderId: "904481277327"
};

@NgModule({
  declarations: [...],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp),
    AngularFireModule.initializeApp(firebaseCredential),
    AngularFireAuthModule
  ],
  bootstrap: [IonicApp],
  entryComponents: [...],
  providers: [...]
})
export class AppModule {}

We’re importing AngularFireModule to initialize the app and create the connection with Firebase, and AngularFireAuthModule to handle anything related to Firebase authentication.

While you’re on that app.module.ts file, remove every reference (if any) of the HomePage class, then go to src/pages/home/ and create a file called home.module.ts populate it with the following info:

import { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { HomePage } from './home';

@NgModule({
  declarations: [HomePage],
  imports: [IonicPageModule.forChild(HomePage)],
  exports: [HomePage]
})
export class HomePageModule {}

Now we’re going to create an authentication listener inside app.component.ts the idea behind it is that when the user opens the app, it tracks if she’s a new user or an already logged in user, if she’s a new user, then the app sends her to the signup page, if she’s an already logged in user, then the app sends her to the home page.

Open app.component.ts and import the authentication module for AngularFire2, then inject it into the constructor and create the authentication listener:

...
import { AngularFireAuth } from 'angularfire2/auth';

@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  rootPage:string;

  constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen,
  afAuth: AngularFireAuth) {

    afAuth.authState.subscribe( user => {
      if (user){
        this.rootPage = 'HomePage';
      } else {
        this.rootPage = 'SignupPage';
      }
    });

    platform.ready().then(() => {...});
  }
}

By the way, if you feel like I’m going to fast, then you should read this post first it explains the entire authentication process in detail, here we’re just going to focus on the Cloud Functions part.

Now it’s time to create our Signup page, users will create an account using this page and we’ll have Firebase Cloud Functions create the database record.

First, while you’re still in the app’s folder, open your terminal and create the signup page

ionic generate page signup

Then go into signup.html and create a form that takes the user’s email and password:

<ion-header>
  <ion-navbar color="primary">
    <ion-title>
      Create an Account
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <img src="http://placehold.it/300x100" />
  <form [formGroup]="signupForm" (submit)="signupUser()" novalidate>
    <ion-item>
      <ion-label stacked>Email</ion-label>
      <ion-input
        formControlName="email"
        type="email"
        placeholder="Your email address"
        [class.invalid]="!signupForm.controls.email.valid && signupForm.controls.email.dirty"
      >
      </ion-input>
    </ion-item>
    <ion-item
      class="error-message"
      *ngIf="!signupForm.controls.email.valid  && signupForm.controls.email.dirty"
    >
      <p>Please enter a valid email.</p>
    </ion-item>

    <ion-item>
      <ion-label stacked>Password</ion-label>
      <ion-input
        formControlName="password"
        type="password"
        placeholder="Your password"
        [class.invalid]="!signupForm.controls.password.valid && signupForm.controls.password.dirty"
      >
      </ion-input>
    </ion-item>
    <ion-item
      class="error-message"
      *ngIf="!signupForm.controls.password.valid  && signupForm.controls.password.dirty"
    >
      <p>Your password needs more than 6 characters.</p>
    </ion-item>

    <button ion-button block type="submit" [disabled]="!signupForm.valid">
      Create an Account
    </button>
  </form>
</ion-content>

Give it a few margins and styles (The invalid class is just a red bottom-margin) inside signup.scss

page-signup {
  form {
    margin-bottom: 32px;
    button {
      margin-top: 20px;
    }
  }

  p {
    font-size: 0.8em;
    color: #d2d2d2;
  }

  ion-label {
    margin-left: 5px;
  }

  ion-input {
    padding: 5px;
  }

  .invalid {
    border-bottom: 1px solid #ff6153;
  }

  .error-message .item-inner {
    border-bottom: 0 !important;
  }
}

Then we’ll move to the code inside signup.ts, the first thing you’ll want to do is to import everything we’ll need:

import { Component } from '@angular/core';
import {
  IonicPage,
  NavController,
  Loading,
  LoadingController
} from 'ionic-angular';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AngularFireAuth } from 'angularfire2/auth';

We’re importing:

  • Loading and LoadingController to create a loading spinner while the account is created.
  • FormBuilder FormGroup Validators will create our form and add validation to it.
  • AngularFireAuth is the authentication module of AngularFire2, we’ll use it to create the new account.

Then create the variables for the loading component and the form, then initialize the form inside the constructor:

public signupForm:FormGroup;
public loading:Loading;
constructor(public navCtrl: NavController, public loadCtrl: LoadingController,
public formBuilder: FormBuilder, public afAuth: AngularFireAuth) {

  this.signupForm = formBuilder.group({
    email: ['', Validators.required],
    password: ['', Validators.compose([Validators.minLength(6), Validators.required])]
  });

}

Now we’ll create the signup function, it needs to call AngularFire2’s create user function and on successful account creation redirect the user to the home page:

signupUser(){
  if (!this.signupForm.valid){
    console.log(this.signupForm.value);
  } else {
    this.afAuth.auth.createUserWithEmailAndPassword(this.signupForm.value.email, this.signupForm.value.password)
    .then(() => {
      this.loading.dismiss().then( () => {
        this.navCtrl.setRoot('HomePage');
      });
    }, (error) => {
      this.loading.dismiss().then( () => {
        console.error(error);
      });
    });
    this.loading = this.loadCtrl.create();
    this.loading.present();
  }
}

We’re taking the email and password the user adds in the form and sending them to Firebase using the createUserWithEmailAndPassword() function, this will do 2 things, 1) it’ll create the new user account, and 2) it’ll authenticate the user, so she doesn’t have to log in again.

Right there you’ll have an error if you try to run the app, because this.navCtrl.setRoot('HomePage'); is a bad link, happened to me and I was clueless, then taking a look at src/pages/home/home.ts I realized I didn’t import the page decorator, so go to home.ts import IonicPage and right before the @Component decorator add @IonPage:

@IonicPage()
@Component({...})

Having fixed that, our app should be working and our users are able to create user accounts, now we’re going to take advantage of Firebase Cloud Functions to listen to our app, and when it notices that a new account is created it’s going to take the user’s ID and create a record of that user in the database.

Firebase Cloud Functions

To set up our cloud functions, we need to first make sure we have the Firebase CLI installed, so open your terminal and type:

npm install -g firebase-tools

Now log into your Firebase console through the CLI, in your terminal type:

firebase login

It will open a browser window to log in, we’re going to store all of our functions code inside the same folder we created our app, so go ahead and create a folder called functions, you can do it from the terminal:

mkdir functions
cd functions

Now, we need to initialize Firebase Cloud Functions, for that, in the terminal (while being in the new functions folder) type:

firebase init functions

It will prompt you to choose an application from your Firebase Console to work with after you choose the application, it will ask you if you want to install the necessary node packages, choose yes.

Also, choose TypeScript as the programming language to use.

Go ahead and in the newly created functions folder look for index.ts delete the placeholder commented code and replace it with:

import { functions } from 'firebase-functions';
import { admin } from 'firebase-admin';
admin.initializeApp();

We’re requiring the functions package to create our authentication listener, the admin package is the nodejs admin SDK for Firebase, which will let you write to the database independent from user permissions.

Then we’re going to create our function, Firebase Cloud Functions has a few triggers in its SDK, we’re going to use the .onCreate trigger inside the user object:

exports.createProfile = functions.auth
  .user()
  .onCreate((userRecord, context) => {
    // Do something after a new user account is created
  });

That code above creates a function that will listen to the app’s authentication module, it will trigger every time a new user account is created and it will run the code we give it.

We want it to create a database reference for that user, so basically to create a node userProfile/<userID> and to populate it with the user’s info, which in this case is just her email:

exports.createProfile = functions.auth
  .user()
  .onCreate((userRecord, context) => {
    return admin
      .database()
      .ref(`/userProfile/${userRecord.data.uid}`)
      .set({
        email: userRecord.data.email
      });
  });

There are a few things happening here:

  • The user data is stored inside userRecord.data.
  • We use the admin SDK to create a reference to userProfile/<userId>.
  • Inside that reference we create a property called email and we populate it with the user’s email address.

And that’s it, just that little bit of code is what we need, now it’s time to deploy it to Firebase servers so that they can handle the code for us.

Open your terminal, you should be inside the MyApp/functions folder, and run the deploy command:

firebase deploy --only functions

Pay attention to the terminal, it will let you know if there are any errors while deploying the function, once it gives you the all clear try creating a new user account, then go to your Firebase Console and you’ll see the database has that node created.

Also, check the Cloud Functions part of the console, it will have the log showing how the function ran, how long it took, and the state of the function.

One thing I want to make emphasis here is that you need to always return a Promise when running a Firebase Cloud Function, return admin.database().ref().set(); returns a Firebase Promise.

When you return a Promise, the function won’t crash or won’t finish until that promise is either resolved or rejected, making sure that the user profile node is created for you.

Are there any other things you’ll like to see in Cloud Functions? Make sure to shoot me a message over twitter, I’m @javebratt, let me know what you’ll like to see and if enough people ask I’ll write a new guide about it :)

Next Steps

If you have any questions don’t forget you can always leave a comment below.

If you want to take a deeper dive on Ionic + Firestore you should go through my FREE course: Build your first Firestore powered Ionic app.