Using Firebase as server-side backend for advanced Verge3D apps

From Verge3D Wiki
Jump to navigationJump to search
With Verge3D and Firebase there are no limits to your creative power!

Sometimes you need to implement quite sophisticated server-side logic to power your Verge3D applications. For example:

  • Authenticated access. The system allows creating user accounts and handles logins (via email/password, social networks, phone etc.)
  • Persistent per-user storage. The users keep their data in the system, for example to preserve product configurations for later use.
  • Multi-user access. 3D interactive supports modifications made by different users (sometimes simultaneously!).
  • Integration. The user places an inquiry on a company website and the system forwards it to some CRM or ERP system for further processing.
  • Gamification. A browser game or a game-like experience handles user's scores/levels/communications.
  • Connectivity. The system sends messages/emails to specified managers/users when some event is triggered in your app.
  • Storing stats. The system collects info about user activity, e.g. detects preferred products for e-commerce app or keeps progress for e-learning apps.
  • Implementing some crazy idea not mentioned above!

Basically, these are the cases when you typically hire a qualified server-side coder (or a team of coders) to do the job along with the system administrator to power-up your server configurations.

Thankfully, there are services that simplify developing server-side logic and fit quite well with the Verge3D paradigm, that is "do not code — make everything with Puzzles!". Such systems are known as Backend-as-a-Service (BaaS), with Google's Firebase as the most prominent example.

Why Firebase?

Firebase is an excellent back-end choice for Verge3D apps as it offers the following features out of the box:

  • Authentication and access control — you can implement signup/login with just a few puzzles
  • Simplistic but really powerful database called Firestore — again, can be easily accessed with puzzles
  • Secure file storage — if you need to store files/images/videos/PDFs
  • Serverless logic — it allows running code snippets automatically in response to user events or network requests
  • Optional hosting — since there is no need to store apps on Google servers, you are free to choose Verge3D Network or your own hosting solution
  • Compatibility with App Manager — you can use Firebase features right on your Verge3D development machine without uploading anything to a remote server
  • Convenient tools for analytics and performance monitoring — very handy and easy to implement

Starting up

In this article we discuss steps needed for running a full-featured Verge3D application powered by a Firebase backend. As an example we use a simplistic 3D configurator with a single model. The model will change its color upon clicking, and this configuration can be saved on the remote server. The saved configurations can be retrieved. One of them can be selected and applied to the model at any time.

The most complex part of the application is handling user accounts. We allow creating new users with email/passwords, logging these users to the system, and displaying the status to the user.

You can try out the resulting app here. We made its source files freely available, so you can make your own projects with minimal efforts.

Creating a Firebase project

To create a new project use the Firebase console (you need a Google account for that):

Create a new Firebase project

and add/register a new web app as part of it:

Create a new Firebase app

Read more on creating web-based projects in the Firebase docs.

On this stage you obtain a pair of Firebase identifiers for your project and app. The actual production will happen in the Verge3D App Manager.

Creating a Verge3D app

Web-based nature of Verge3D presents a very exciting opportunity. You don't need to create a web application specifically for Firebase because you already created one with Verge3D. For example, to design a chair configurator like that:

Firebase-based 3D interactive chair configurator

... all you have to create a configurable chair model which can be rotated/scaled and a bunch of UI buttons in the top-left corner of the app.

The logic for the app is as follows:

  1. The user creates a new account using email/password and signs into it
  2. The user clicks on the chair to select a random color for the upholstery
  3. If the color looks good, it can be saved into the personal dashboard under some name (e.g. "Chair for my lounge")
  4. The user can restore the saved color at any time: immediately, or after reloading the page, or from some other device/browser, etc.

Let's explain Puzzles for randomizing upholstery color right away:

Puzzles to change color for the chair configurator

Here we use the when clicked puzzle to register mouse/touch events. Upon such event, a random color (from 0-1 range) is applied to the model thanks to set color.

Creating HTML for the app

The configurator app is operated using 5 (4 visible + 1 hidden) buttons, located in the top-left corner of the app:

Buttons for Firebase-based chair configurator

  • Signup button — used to create a new account
  • Login/Logout buttons — used to login/logout. These are 2 distinct buttons, but only one is visible.
  • Save button — displays dialog to save chair configuration
  • Load button — displays dialog to load named configuration from the list

Also there is an email text field. It displays "Please signup / login" text if user is not yet logged in, or user email otherwise.

Let's discuss everything in detail.

Buttons and text field

We use add HTML element, set prop, and set style Puzzles to create all 5 buttons and a text field:

Buttons Puzzles logic

These controls are built from the <div> HTML elements arranged into two rows (first-row-buttons and second-row-buttons).

The buttons have the following IDs:

  • signup-button
  • login-button
  • logout-button
  • save-button
  • load-button

To make the logic more compact, we create a new CSS class .button which will be used to assign styles for all buttons at once:

Buttons CSS logic

The text field login-label is used only once, so we assign its style right after creating the corresponding <div> element.

Dialogs

The Puzzles for these dialogs look complicated but the idea is the same. As we did with buttons, we use the same 3 puzzles (add HTML element, set prop, and set style) to compose dialog windows and their content.

HTML standard offers modern <dialog> element to simplify building dialogs. Inside we place a web form (<form>) which represents a collection of <div>, <input>, <button>, and <select> elements. The form elements are positioned using the grid layout — hence we use display: grid in the corresponding set style puzzles.

Starting with Verge3D 4.13, you can use the HTML Dialogs library to speed up creating conventional dialog windows.

Signup and login forms

The login-dialog contains two <input> elements and two <button> elements. It is used for both signup:

Signup dialog look

and login:

Login dialog look

The Puzzles that create it:

HTML Puzzles for creating login dialog

OK and error messages

The ok-dialog contains 1-2 text lines (rendered as <div> elements) and the "OK" <button> element. It is used to display information and errors to the user:

Ok dialog look

The Puzzles that create it:

HTML Puzzles for creating OK dialog

Saving configurations

The save-cancel-dialog contains an <input> element and two <button> elements. It is used to display the dialog for saving chair configurations to the remote database:

Save dialog look

The Puzzles that create it:

HTML Puzzles for creating save dialog

Loading configurations

The load-cancel-dialog contains a <select> element and two <button> elements. It is used to display the dialog for loading chair configurations obtained from the remote database:

Load dialog look

The Puzzles that create it:

HTML Puzzles for creating load dialog

Loading Firebase modules

To interact with Firebase you need to import several external libraries into your app:

External libraries are loaded from the init tab with the load library puzzle:

Loading firebase modules

Once loaded, the corresponding APIs will be available on the rest of Puzzle tabs via the firebase namespace (see below).

Firebase bindings

Firebase APIs are made for JavaScript (or other languages), not Puzzles. To convert the corresponding method calls from JavaScript to Puzzles you need to implement several bindings — in other words, connectors between Puzzles and code:

Firebase Puzzles bindings

For our configurator app we use the following methods:

  • initializeApp — initialize a Firebase app
  • createUserWithEmailAndPassword — create a new user with email and password
  • signInWithEmailAndPassword — sign-in with email and password
  • onAuthStateChanged — handle authentication status changes
  • signOut — sign-out from the account
  • setUserDoc — set a document in the Firestore database
  • getUserDoc — retrieve a document from the Firestore database

Implementing these in your own Firebase app might be cumbersome, so you better copy-paste them from the freely available project sources.

Promises

Many Firebase methods do not return their results immediately. Instead they return special promise values which then should be waited for (resolved) to obtain the corresponding method results. Thankfully, there are special wait promise and promise value puzzles which will help you to manage promise values in a visual way. We use these puzzles extensively in our chair configurator app.

App initialization

Once you create a Firebase app, it will provide the configuration which looks as follows:

const firebaseConfig = {
  apiKey: "AIzaSyB5YOputCtfa437SFluX0EGnBP7i67qyYo",
  authDomain: "test-7bdf5.firebaseapp.com",
  projectId: "test-7bdf5",
  storageBucket: "test-7bdf5.firebasestorage.app",
  messagingSenderId: "741340162909",
  appId: "1:741340162909:web:c1550f2ab43e2bdde896b4"
};

This configuration should be passed as a dictionary value into the initializeApp API method:

Puzzles to initialize a Firebase application

Implementing auth logic

We'll use the same dialog window for handling user signups and logins. This will reduce copy-pasting and make your code more robust.

Showing signup dialog

Here we register the click event listener for the "Signup" button with the on event of puzzle. Once the user clicks on that button, we set the dialog variant variable to "SIGNUP" (we'll use it later), set dialog text, set text for the dialog's submit button, and finally display the actual dialog with the call method - showModal puzzle.

Puzzles to show signup dialog

Showing login dialog

Here we register the click event listener for the "Login" button with the on event of puzzle. Once the user clicks on that button, we set the dialog variant variable to "LOGIN" (we'll use it later), set dialog text, set text for the dialog's submit button, and finally display the actual dialog with the call method - showModal puzzle.

Puzzles to show login dialog

Processing signup

As we stated earlier, we use the same dialog for signup and login. The actual behavior is managed by the dialog variant variable.

Our common processing logic starts with the on event of puzzle used to handle the submit event of the dialog form. In the beginning we also change our cursor to a waiting symbol with the set style puzzle.

Puzzles to process signup

The SIGNUP part of the event collects the user credentials from the form and executes the createUserWithEmailAndPassword API method. This method returns a promise value that should be waited for.

What happens next depends on whether the sign-up process finishes with success or error. In both cases we set the corresponding message as the first text line of the ok-dialog dialog (ok-dialog-text). In case of error, we additionally extract the error code from the resolved promise to set an error message for the second line of the dialog (ok-dialog-text-2) and display it to the user (set styledisplayblock).

Finally, we return the mouse cursor back to normal, close our login dialog and open the ok dialog.

Processing login

This is the second half of our submit event handler:

Puzzles to process Firebase login

This part is responsible for managing the LOGIN variant of our dialog. Similarly to the first part, it collects the user credentials, but this time executes the signInWithEmailAndPassword API method.

In this snippet we do not use the returned user information (except for handling errors), since the actual login status modification is processed by the onAuthStateChanged (see below).

Listening for auth state changes

Firebase offers a really convenient method called onAuthStateChanged.

Puzzles to handle Firebase auth state change

It listens for changes to the user's sign-in state in real time calling the provided callback procedure. For example, it may be used to automatically restore the login session upon the app reloading.

The auth state changed callback procedure takes the user info object as its parameter and does the following:

  1. Checks if the user object is defined or null. Defined means the user just successfully logged in, while the null value means he just logged out
  2. Updates the login-label text field
  3. Sets logged uid variable with the current user ID
  4. Swaps the "Login" and "Logout" buttons (as we discussed earlier, only one of them can be visible at once)
  5. Makes the second row of buttons ("Save" and "Load") opaque or semi-transparent. Semi-transparent buttons appear disabled, so the user won't click them mistakenly if not logged in

Performing logout

This logic is simple — upon clicking on the Logout button it executes the signOut API method:

Puzzles to perform Firebase logout

Implementing save/load logic

To save/restore configurations we use the remote database called Firestore. Firestore is a powerful, fast, yet really easy-to-use database that is available from any Firebase project (hence the similarity between the two names). This database stores documents which are very similar to Puzzles' dictionary values (do not confuse these with Word or Excel documents).

Documents are stored/retrieved to/from the database by their IDs (unique strings, e.g. "miJebfFBqNa4QCJWyeV1"). Also, the Firestore documents are grouped into collections (this is mandatory, each document must be an item of some collection, e.g. "users").

In our Chair configurator app we store configurations on a per-user basis. For document ID we use the user's unique ID obtained during authentication (it is stored in the logged uid global variable). The document looks as follows:

{
  configurations: [
    {
      name: "First Configuration",
      upholsteryColor: "#RRGGBB"
    },
    {
      name: "Second Configuration",
      upholsteryColor: "#RRGGBB"
    }
  ]
}

To see how your database looks like, go to ProjectDatabases & StorageFirestore in your Firebase console:

Firestore admin UI

Don't worry if you make mistakes in your app. You can always cleanup the entire database and start from scratch.

Saving configurations

Having designed the database structure we can proceed to the actual Puzzles to store the user's configuration.

Puzzles to save configuration

Let's explain this scenario step by step.

In the beginning we use the usual pair of puzzles on event of and call method to show the save-cancel-dialog dialog:

Save dialog look

Then the user clicks on the "Save" button, the submit event is sent to the save-cancel-dialog-form form. In the event handler we do the following:

  1. Change cursor to a waiting symbol as communicating with the remote server takes time
  2. Prevent the form submission. For dialog-based forms this means the dialog won't be closed immediately.
  3. Retrieve the current configuration name
  4. Execute the getUserDoc method to retrieve the document associated with the user ID
  5. Retrieve the configurations key (aka field) from the document and save it to the list represented by the configs variable (remember, the document structure is represented by a dictionary)
  6. If the configurations list is present, we try to find an existing item with the name equal to the saved name. If the configurations list is not present, we create an empty one.
  7. If the saved name item is present in the list, we set the upholsteryColor for the corresponding item
  8. If the saved name item is not present in the list, we create a new item (also a dictionary) and set its name and upholsteryColor keys
  9. The configurations list is ready, so we create a new document and execute the setUserDoc method to store it in the remote database
  10. Finally, we return the cursor style back to normal (set stylecursordefault) and close the dialog

Loading configurations

Logic for loading configurations consists of two important snippets:

  1. When the user clicks on the "Load" button from the top-left corner of the app, we open the "Select Configuration" dialog, retrieve the configurations from the database, then populate the dialog's option selector
  2. When the user clicks on the "Load" button from the dialog (do not confuse it with the previous "Load" button), we assign the selected upholstery color on our Chair model

Puzzles to load configuration

The first step is similar to how the getUserDoc method was used during the saving stage. The difference is that instead of updating the configs list we fill the dialog's <select> element with options.

Each option is represented by the <option> element with the value property set to the corresponding configuration name. The option's color is stored in the dataset property as a CSS color (e.g. #ff0000 for red)

On the second step we retrieve the active <option> element with the query selector puzzle, extract color from its dataset property, then apply the extracted color to the model with the set color puzzle.

Project sources

The source files of the Chair configurator project are freely available here. Just download the provided .zip archive and drag and drop it into the App Manager.

Inside you will find the following folders and files:

  • public — source files for the Verge3D application (made for better compatibility, you can store your app in the root folder as well)
  • firebase.json — Firebase project configuration
  • firestore.rules — security rules for the Firestore database
  • firestore.indexes.json — Firestore indexes (not used)

Deploying your own project

For deploying projects, the official Firebase instructions suggest using the firebase CLI utility which is very efficient, but require entering commands in the terminal. If you prefer a visual approach, perform some preparations via the Firebase console:

  1. Enable email/password authentication with ProjectSecurityAuthenticationGet started
  2. Create a Firebase database with ProjectDatabases & StorageFirestoreCreate database (select all options by default)
  3. Append the Firestore security rules (e.g. by copying/pasting the contents of the firestore.rules file) with ProjectDatabases & StorageFirestoreRules

After that, you can deploy the project to Verge3D Network or choose your own hosting solution. Here is the Verge3D Network link for you to check it out.

Next steps

Firebase comes with many advanced features. However, using them won't give you much trouble — Firebase is well-documented, so AI assistants will be exceptionally good at helping you out.