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);
}
}