Create Cloud Functions in Firebase

Authorization, CRUD, queries, most of the things come out of the box with Firestore. However, sometimes there is a need to make some modifications to the objects, which would be too sensitive to do in the browser. To demonstrate, let's look at the following object:

{
    imageUrl: '',
    price: 9.90,
    title: 'Homemade Soup'
}

When the user adds a new product, I want to calculate a slack property for it, so products could be displayed by their slack in the URL. This should be unique, but also changeable. I don't want my frontend to calculate this, as a duplicated or missing key could cause problems. To do this on the backend, use a cloud function. Using Firebase CLI, it takes only a few minutes to set this up.

npm install -g firebase-tools
firebase login 
firebase init functions

When the installer asks, choose TypeScript, add TSLint, and also install npm dependencies.

It will create a functions/src folder, with an index.ts in it. The function below will add a product to my Firestore database, with my additional property attached.

exports.addProduct = functions.https.onCall(
    (data, context) =>
       admin
           .firestore()
           .collection("/products")
           .add({
                ...data,
                slack: slackify(data.title)
            })
      )
      .then((docRef) => docRef.id) // be careful what returns
);

Be extremely careful with the last line though. It took me some time and obscure error messages to find out what was happening. When the function inside functions.https.onCall returns a promise, Firebase tries to serialize the returned object. This function returned a DocumentReference, what as it turned out, is not serializable and causes a stack overflow. To tackle this, I return the id of the newly created document.

When everything is done, it's time to deploy.

firebase deploy –-only functions

On the Firebase site, you can inspect executions and error stacks.

Calling a Cloud Function in Angular

Like Firestore or all the other Firebase modules, FireFunctions has to be imported the same way, in your module.

import { AngularFireModule } from "@angular/fire";
import { AngularFireAnalyticsModule } from "@angular/fire/analytics";
import { AngularFirestoreModule } from "@angular/fire/firestore";
import { AngularFireFunctionsModule } from "@angular/fire/functions";

@NgModule({
    declarations: [
        AppComponent,
        //...
    ],
    imports: [
        // ...
        AngularFireModule.initializeApp(firebaseConfig),
        AngularFirestoreModule.enablePersistence(),
        AngularFireAnalyticsModule,
        AngularFireFunctionsModule,
    ],
    bootstrap: [AppComponent],
})
export class AppModule {}

In your service, inject AngularFireFunctions, and everything is ready to go:

//...
import { AngularFireFunctions } from "@angular/fire/functions";

@Injectable({
    providedIn: "root",
})
export class ProductService {

    constructor(
        private fireFunctions: AngularFireFunctions
    ) { }

    addProduct(product: CreateProduct): Observable<any> {
        return this.fireFunctions.httpsCallable("addProduct")(product);
    }
}