Using Firebase for Backend-as-a-Service: Pros and Cons

Julien DemangeonAlexis Janvier
#js#serverless

During a recent customer project, we have faced with an important time constraint. As a consequence, we were forced to question the way we work without giving up features.

Usually at Marmelab, our project setup (including continuous delivery, deployment and user management) approximately takes 4 man days. For this project, this would represent about 10% of all the time we had to achieve our objectives. Indeed, we had 1 month, i.e. 20 man days for two developers to complete the project.

As a result, in agreement with the customer, we chose to use the Firebase platform to focus on the business logic, without wasting our precious time on trivial tasks.

Firebase, What and Why?

Firebase is a development platform which offers a large set of products and covers many needs. It is backed by Google, and trusted by many well-known companies like Shazam, The New York Time, or Alibaba.

Support companies

The platform defines itself as a RAD (Rapid Application Development) oriented platform, i.e. it allows to get rid of common app development challenges and focus on features and user experience.

Among the main features of Firebase, we were particularly interested in:

  • Firestore: Benefit from a cloud hosted, realtime and offline-first document database
  • Cloud Functions: Write custom backend code without managing and scaling your own servers
  • Cloud Storage: Cost-Effective File Storage in the cloud
  • Authentication: Manage users, signin, login and password recovering easily
  • Hosting: Simply upload our web assets and benefit from a CDN and SSL Secured web experience

Because of its large base of features, Firebase was the best suited solution according to our time constraints and our ambitious objectives. In the next chapters, we'll describe in more detail the pros and cons of this technical choice.

What We Liked

When we started developing the project, we were immediatly able to use the extensive documentation that Firebase offers. Moreover, Google highlights its platform through many videos on its Youtube Channel, which has 200K members. Everything is done to make the developer's life easier, we can't blame them for that.

Firebase Youtube Channel

As we expected, project initialization using Firebase is very fast. Indeed, on the very evening of the project launch, customers were able to create an account and login on the staging application. We were also able to set up the entire continuous integration chain on the same day using Travis.

Hosting configuration is very easy to take in hand. You just have to fill some keys of the hosting section of the firebase.json file.

{
  "hosting": {
    "public": "build",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

Moreover, Firebase allows to tweak numerous other options, allowing to create custom redirects from regex matchs, rewrite paths to Cloud Functions, configure custom cache control and headers, etc.

In the next few days, we got into the meat of Firestore, the successor of the legacy Firebase Realtime Database product, to store our data. Firestore is a scalable, realtime and offline-first NoSQL database, which allows to build fast applications regardless of the network connectivity latency.

The Firebase team did everything to ensure a smooth and fast user experience. So, the logic is based entirely on subscriptions. In our case, we used the RxFire library to link RxJS (the famous Reactive Extensions Library) to Firebase.

As a result, our code is incredibly clear and concise when associated with ReactJS hooks. Here is an example:

import React, { useState, useEffect } from 'react';
import { collection } from 'rxfire/firestore';
import firebase from 'firebase/app';
import 'firebase/firestore';

const app = firebase.initializeApp({
  // ... config
});

const createEventsObserver = userId => collection(
  app.firestore().collection('events').where(`members.${userId}`, '>', '')
);

const Home = ({ user }) => {
    const [events, setEvents] = useState([]);

    useEffect(() => {
        const eventsObserver = createEventsObserver(user.uid).subscribe(setEvents);
        return () => eventsObserver.unsubscribe();
    }, []);

  return (
    <ul>
      {events.map(event => (
        <li key={event.id}>{event.name}</li>
      ))}
    </ul>
  );
};

export default Home;

As mentioned above, Firestore provides an integrated offline mode thanks to a synchronized local database. This also means that it is possible to benefit from Optimistic Rendering without any additional effort from the developer.

Finally, thanks to Cloud Functions, we were able to develop custom code for the backend everytime we were blocked. We enjoyed this feature very much.

What We Didn't like

The other side of the coin of this "turnkey" solution is that you are completely dependent on an ecosystem you can't control: servers, SDK, data persistence... Not to mention that this ecosystem is proprietary and potentially expensive.

That's a prerequisite that you must accept as soon as you decide to use Firebase. It's the price to pay for the ease and speed of development that the solution offers. It's therefore difficult to blame Firebase for it and to consider it as a negative point.

But beyond this question, several things displeased us during the development of the project.

The Requirement to Operate on a NoSQL Database

At Marmelab, we have adopted the habit of starting projects with a PostgreSQL database, among other things because it allows us to have a fairly flexible "noSQL" approach at the beginning of the project, and eventually switch to a relational approach if the project requires it. When starting a Firebase project, you can only choose the NoSQL approach, with its advantages and disadvantages.

You can't go back

So we adapted. We used more and more lambdas. Firebase even offers helpful solutions for classical NoSQL difficulties, such as sub-collections, or exotic index management.

There was also a kind of behavioural side effect induced by the fact that the solution could become a paying solution. We have spent far too much time trying to avoid network calls or lambdas that are too specialized. Indeed, the limitation of the free version and the pricing of the paid version are based on calls to the database or the number of lambda executions.

Frankly, IDD (Invoice Driven Development) is not a good development methodology.

The Complexity of Permissions Management

Another unpleasant point when using Firebase is the management of the permissions on the data stored there. First, this permission management is based on a specific syntax that is not always very easy to use.

But it is also restrictive since you have to manage and synchronize permissions from a separate file. It is also a problem for testing, as we will see later.

Here is an example of rules from a firestore.rules file.

// firestore.rules

function isSignedIn() {
  return request.auth != null;
}

function getEventRole(rsc) {
  return rsc.data.members[request.auth.uid];
}

function isEventOwner(rsc) {
  return getEventRole(rsc).matches("^owner[:](organiser|provider)$");
}

function isValidEvent(rsc) {
  return rsc.data.title.size() < 60;
}

function isValidNewEvent() {
  return resource == null
    && isValidEvent(request.resource)
    && isEventOwner(request.resource);
}

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{document=**} {
    	allow read: if isSignedIn();
    }
    match /events/{document} {
      allow write: if isValidNewEvent() || (isValidEvent(resource) && isEventOwner(resource));
      allow read: if isSignedIn() && getEventRole(resource) != null;
    }
  }
}

As you see, the rule syntax can quickly become cumbersome and difficult to maintain. Fortunately, developers from the community have graciously developed tools to generate these awful rule files based on more legible code.

The "Real-Time" Architecture Is Difficult to Abstract

One of Firebase's great strengths is its real-time capacity. It's with this feature in mind that we implemented the web application, facilitated by RxJS.

Realtime Capabilities

We still had in mind the idea of limiting the dependency to Firebase as much as possible, so we did everything we could to remove the firebase SDK from the implementation of user interfaces. It has not always been easy. And it was perhaps futile: the replacement of Firebase by another real-time technology (such as the use of websocket or GraphQL subscriptions) seems complicated despite our desire to abstract Firebase...

It's Hard to Test

This is one of the most negative points of the Firebase solution: the implementation of automated tests! Lambdas are JavaScript (or TypeScript) functions that, in theory, can easily be tested unitarily. But they must be sent to Google's servers to be executed.

To be able to set up functional tests on the application, it is therefore necessary to reproduce the environment in which lambdas are executed within the continous integration. Even if Google provides Docker images to do this, we couldn't get them to work in a reasonable amount of time - so our lambda functions could not be tested automatically.

The same problem arises for the implementation of automated functional tests for the management of rights on the database...

The Slowness of the Free Version

One lhe last point, less annoying than the others, but which caused us problems during the demonstrations: the slowness of the free solution. Indeed, the lambda of the free version is in cold-start, which has had a very negative impact on the demonstrations given to users. Nothing prohibitive, but it's annoying when you have to justify the slowness of your application.

Conclusion

Despite the negative points mentioned, Firebase is in our opinion an excellent tool. Being able to demo a basic application with user management and real time updates in less than a day is huge. And the development experience is much simpler and more enjoyable than many similar solutions like AWS Amplify.

For a side-project, a very simple project with few users, or for a throwaway POC, it's an excellent solution, and very cheap because the free tier is generous.

However, for long-term developemnt, we think it is much more problematic. First of all, if the solution is to be sustainable, the economy of the boostrap days should not be significant. Especially since the time saved in setting up the first interfaces could be lost when handling a complete continuous integration stack (unit and functional tests).

Secondly, if the Firebase solution offers a highly scalable potential, the costs incurred are very difficult to estimate. It is therefore complicated to predict the cost of the solution in case of success, which does not facilitate the sustainability.

However, the "real-time" architecture of the database and the implementation of a reactive architecture is a real discovery and a real good knowledge to put in your developer toolbox. This made us want to look at open-source alternatives such as CouchDB.

Did you like this article? Share it!