Serverless application with Firebase

Feb 23 2018

This Is a tutorial that explains step by step how to write a serverless application.

In this tutorial, I will use Angular and Firebase, The developer experience of the both are so painless that it is really fun. Especially if you compare it to other serverless solutions such as AWS Lambda.

Firebase

Firebase is a mobile and web application development platform. It acquired by Google and has grown and expanded their services.
Firebase provides the following services:

  • Authentication
  • Database
  • Storage
  • Hosting
  • Functions

In addition, it provides push notifications (FCM), analytics, logging and monitoring, and more. It integrates with other Google services quite easy.

First application with Firebase

We will create a very simple app with Firebase that will demonstrate how to work with firebase in a nutshell.

The application is called Grocelist. It will use the following Firebase services:

  • Authentication
  • Database - Adding and removing groceries from a list.
  • Functions - updating a bank of groceries for autocomplete.

Our first version of the application will only with Angular and Firebase without function, after it is working we will add the functions.

In order to work with Firebase, we need to connect to Firebase console and create a new project, let’s call it ‘Grocelist’.

Then we will create a database for the app, we will use the new Firebase database - Firestore (go to the database configuration and provide “Cloud Firestore”).

Now it’s time to create the angular app, it is a pretty simple application with one component (app component)

ng new groceries

Add firebase & angularfire2 libraries:

npm install angularfire2 firebase --save

This will allow us to connect to firebase almost out of the box.
We will change the following files to show our list in firestore:

app.component.ts:

import { Component, OnInit } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';

interface Grocery {
  name: string;
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

  groceriesCol: AngularFirestoreCollection<any>;
  groceries: any;
  name: string;

  constructor(private afs: AngularFirestore) { }

  ngOnInit() {
    this.groceriesCol = this.afs.collection('groceries');
    this.groceries = this.groceriesCol.snapshotChanges()
    .map(actions => {
      return actions.map(a => {
        const data = a.payload.doc.data() as Grocery;
        const id = a.payload.doc.id;
        return { id, data };
      });
    });
  }

  addGrocery() {
    this.afs.collection('groceries').add({'name': this.name});
    this.name = '';
  }

  delete(grocery) {
    this.afs.doc('groceries/'+grocery.id).delete();
  }
}

It is a very comprehensible code that uses AngularFirestore to register on events, add and remove items from ‘groceries’ collection.

app.component.html:

<div class="container">
  <div class="input">
    <input class="text" type="text" (keyup.enter)="addGrocery()" [(ngModel)]="name" name="name" placeholder="Add a Grocery...">
  </div>
  <div class="list-container">
    <div class="list" *ngFor="let grocery of groceries | async">
      <div class="item" (click)="delete(grocery)">
        <div >{{ grocery.data.name}}</div>
      </div>
    </div>
  </div>
</div>

Add, delete and show the list of items.

app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { environment } from '../environments/environment';

import { AngularFireModule } from 'angularfire2';
import { AngularFirestoreModule } from 'angularfire2/firestore';
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AngularFireModule.initializeApp(environment.firebase, 'grocelist'),
    AngularFirestoreModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Add AngularFirestoreModule, initialize it. In order to give it the configuration of our Firebase project, we add it to the environment file with the credentials from Firebase (project overview -> Add Firebase to your web app -> copy the config:

environment.ts (replace with your config variables):

export const environment = {
  production: false,
  firebase: {
    apiKey: "XXX",
    authDomain: "grocelist-XXX.firebaseapp.com",
    databaseURL: "https://grocelist-XXXX.firebaseio.com",
    projectId: "grocelist-XXXX",
    storageBucket: "grocelist-XXXX.appspot.com",
    messagingSenderId: "XXXXX"
  }
};

Then we execute

ng serve

And we get a running application with Firebase as a backend, we are running serverless!

Adding authentication

Firebase gives us authentication out of the box, it supports several authentication methods such as email/password, facebook, google, twitter, etc. we will use google.
Just go to ‘Authentication’ section, press ‘set up sign-in method’ and choose google - enable.

We will update our code to use authentication:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { environment } from '../environments/environment';

import { AngularFireModule } from 'angularfire2';
import { AngularFirestoreModule } from 'angularfire2/firestore';
→> import { AngularFireAuth } from 'angularfire2/auth';
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AngularFireModule.initializeApp(environment.firebase, 'grocelist'),
    AngularFirestoreModule,
    FormsModule
  ],
  → providers: [AngularFireAuth],
  bootstrap: [AppComponent]
})
export class AppModule { }

Again we are using 'angularfire2’ out of the box features instead of implement it ourselves.

app.component.ts

import { Component, OnInit } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { AngularFireAuth } from 'angularfire2/auth';
import * as firebase from 'firebase/app';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';

interface Grocery {
  name: string;
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

  groceriesCol: AngularFirestoreCollection<any>;
  groceries: any;
  name: string;

  constructor(private afs: AngularFirestore, public afAuth: AngularFireAuth) { }

  ngOnInit() {
    this.groceriesCol = this.afs.collection('groceries');
    this.groceries = this.groceriesCol.snapshotChanges()
    .map(actions => {
      return actions.map(a => {
        const data = a.payload.doc.data() as Grocery;
        const id = a.payload.doc.id;
        return { id, data };
      });
    });
  }

  addGrocery() {
    this.afs.collection('groceries').add({'name': this.name});
    this.name = '';
  }

  delete(grocery) {
    this.afs.doc('groceries/'+grocery.id).delete();
  }

  login() {
    this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider());
  }
  logout() {
    this.afAuth.auth.signOut();
  }
}

app.component.html

<div class="container">
  <div *ngIf="afAuth.authState | async as user; else showLogin">
    <div class="header">
      <div class="caption">Hi {{ user.displayName }}!</div>
      <a href="#" (click)="logout()">Logout</a>
    </div>
    <div class="input">
      <input class="text" type="text" (keyup.enter)="addGrocery()" [(ngModel)]="name" name="name" placeholder="Add a Grocery...">
    </div>
    <div class="list-container">
      <div class="list" *ngFor="let grocery of groceries | async">
        <div class="item" (click)="delete(grocery)">
          <div>{{ grocery.data.name}}</div>
        </div>
      </div>
    </div>
  </div>
  <ng-template #showLogin>
    <p>Please login.</p>
    <a href="#" (click)="login()">Lo gin with Google</a>
  </ng-template>
</div>

And now we have an authentication and login in our application, without almost any work. We can use the user data any way we like, but for this example I will use it only to display the user name.
Notice that in real application it is better to write the auth code in a dedicated service, but here for simplicity, it is in the component.

Using functions

We have a great web application with database and authentication. It may be enough for applications, but what if we would like to add some logic to the server?

Firebase Functions are here to serve us. Firebase functions are built in on top of google cloud functions and we have the Firebase tools to facilitate the deployment of them to the cloud.
As I mentioned before, FaaS are event-driven. Firebase functions are triggered by the following events:

  • Cloud Firestore Triggers - onCreate,onUpdate, onDelete, onWrite.
  • Realtime Database Triggers - same as Firestore
  • Firebase Authentication Triggers
  • Google Analytics for Firebase Triggers
  • Crashlytics Triggers
  • Cloud Storage Triggers
  • Cloud Pub/Sub Triggers
  • HTTP Triggers

As you can see, it covers almost every common scenario of a typical application. Of course that it is only working with Firebase services, so you cannot invoke them from other places such as AWS etc.

Writing the function can be done only in JavaScript or TypeScript. In AWS Lambda, for instance, the selection is wider: JavaScript, Python, Java, C# and Go.

The best way to deploy the functions to Firebase id to use the Firebase SDK.
You simply write your functions and let the SDK deploy it for you.

npm install -g firebase-tools
firebase login
firebase init  # pay attention to choose ‘dist’ as public folder

You will have all your projects to choose from, select Groelist.
In the generated code, open index.html and add the functions there.

Adding item to global items list

We will write a function that will add any item to a global collection that will be used for suggestions. We will use the onCreate method on Firestore:

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

exports.addToBank = functions.firestore.document('/groceries/{documentId}')
.onCreate(event => {
  console.log('grocery created', event.data.data());
  const groceryName = event.data.data().name;
  const bankRef = admin.firestore().collection('bank_of_groceries');
  const query = bankRef.where("name", "==", groceryName);
  query.get().then(querySnapshot => {
    if (querySnapshot.empty) {
      console.log('Add to bank', groceryName);
      bankRef.add({name: groceryName})
    }  
  })
});

The method addToBank takes the newly created item and added it to the bank_of_groceries if it is not already there.

I added add typeahead functionality to the client so the user can choose from the previous.
In order to deploy:

ng build --prod
firebase deploy

Your app and functions are deployed to firebase

You can see here a live demo.

All the code is available in here.

Conclusion

Writing an application with firebase is very fast and easy. The installation and configuration that we need to do it are very little.
In just a few minutes we have a server that is scalable, stable and reliable.
Firebase is hosting all the services that we need: hosting, database, functions, authentication and more.

All we have to do is write are awesome applications!

Amitai B.
Software Developer
Back to Blog