(Part 1) Overengineering for a Startup: How not to use Google Firebase

Updated Oct 17, 2023
tl;dr: Startups need to build quickly. They need to focus their efforts on their core value proposition. Spending time on technical fluff is not focusing on the core value proposition. Leveraging tools that allow startups to build quickly and spend less time on technicalities = good. Firebase is kiff. Firebase allows us to build out applications quickly and removes a lot of technical overhead. Startups should leverage Firebase and similar tools to build quickly so that the business problem is center focus and value prop is fulfilled.

I was fortunate enough to be selected to speak at the Google I/O Extended Cape Town conference earlier this year. The title of my talk for the conference was “Overengineering for a Startup: How not to use Google Firebase”.

In this talk, I shared some of the learnings building for a startup. The current tech stack we’re using (the overengineerd one alluded to, which definitely comes with its pros and cons), the things I would’ve done differently with the learnings I’ve made along the way and a dive into some of the really cool features of Firebase.

The goal of this talk was to help other technical co-founders out there learn from my mistakes and steer them towards leveraging existing technologies to build and iterate quickly. In this series, I will share the content of my talk in 2 parts:

  1. In this post, we explore some of the really cool features of Firebase as a product, and how Firebase can help you win as a startup by allowing you to focus on your core value proposition and build quickly.
  2. In the next post, for the nerdier reader, we’ll look at our current tech stack, how I might have “overengineered” when approaching the build for our startup, and the pros and cons of this setup (don’t get me wrong, I still think our tech stack is sick!).

The startup dilemma

Building a product is hard. Building a product for a startup is even harder. Not only do you need to think about the technical problems related to the software and building the thing, but you also constantly need to think about the business problems and how to make sure you’re building the right thing. And so, in the context of a startup, I advocate for removing as much technical overhead as possible. Rather, opt for simple development pipelines that allow for rapid iteration by leveraging existing technologies.

One such technology is Google Firebase.

meme This meme describes exactly what I hope to impart. Sometimes, the simplest and most overlooked tools can be the most powerful.

What Makes Firebase Unique?

Firebase is what is called a “Backend-as-a-Service” (BaaS) product. It is a suite of tools that allow you to build and deploy applications quickly, without having to worry about infrastructure, managing complex CI/CD pipelines and building out custom solutions for things like authentication and a host of other common application needs (all the “backendy” stuff).

In particular, two of the core features that I believe really make Firebase unique as a product are:

  1. Security rules
  2. Cloud functions

More on these later, but first, to understand why Firebase is revolutionary we must understand why backend and frontend separation has traditionally been so important.

A brief history on backend/frontend separation

Historically, separating the backend and frontend of an application has been best practice. There are many reasons why this is the case, but the few that I want to highlight are:

  1. Security
  2. DB interaction
  3. Scalability
  4. Framework independence
  5. Reusability

Security

Sensitive information such as API keys and other credentials should never be stored in the frontend. Furthermore, having a separate backend allows for custom auth interceptors to be implemented to manage who can access which business logic, and ensures that proprietary business logic is hidden from the public. We can also have and extra degree of control over the backend by specifying things like which domains, or even IP addresses, can access the backend.

DB interaction

Every programmer is familiar with the concept of a SQL injection attack. When then frontend directly interacts with the database, with no authentication layer on top, it makes an application vulnerable to this type of attack. Thus, it has been notoriously bad practice to have any database interactions in the frontend. Having a dedicated backend prevents this problem by ensuring that any db interactions are routed through the backend, which can validate the input, authenticate the request, and then only perform the action.

Scalability

Separating frontend and backend offers enhanced scalability by allowing independent scaling of out backend. It also allows for centralized updates to ensure that all clients receive updates to backend logic in realtime. Typically, the most computationally intensive work takes place on the backend and so as the number of users on your platform increases, scaling the frontend (which is static content) isn’t as mission critical as scaling the backend. This independence is critical in achieving this.

Framework independence

By having a well defined API layer on top of your backend, it ensures that the frontend can be built using any framework, and also ensures that the frontend doesn’t care about the framework in use in the backend. You can change the database you use, implement authentication interceptors, add a caching mechanism, and the frontend will continue to function the same, interacting with your APIs (as long as you don’t make any breaking changes) and requiring no changes to the code.

Reusability

A well defined API layer on top of your backend also allows for the same backend to be used by multiple frontends. This is particularly useful if you have a web app and a mobile app, or even multiple web apps. It prevents a duplication of effort and ensures that the same business logic is used across clients for a consistent user experience.

How Firebase alleviates some of these issues

As the name “Backend-as-a-Service” implies, Firebase handles the typical complexities of setting up a backend. It is designed in such a way that it can be used directly in your frontend code. What has traditionally been a big no-no, having frontend code and backend logic live together, is overcome by some of the unique features Firebase has to offer. In particular, with Firebase, the following pitfalls are addressed:

  1. Security
  2. DB interaction
  3. Scalability
  4. Framework independence
  5. Reusability

Two of the key features that contribute to this are Cloud Functions and Security Rules.

How does Firebase address security, DB interaction and scalability?

Security Rules

Firebase security rules uses a delcartive JSON/Javascript type language to specify how user’s can interact with your database and with storage. An example might look as follows, with a description following:

service cloud.firestore {
  match /databases/{database}/documents {

    match /posts/{postId} {
        // write if the UID of the document being created matches 
        // the UID of the user making the request
        allow write: if requestMatchesUID();
        // update if the UID of the existing document requested to be updated matches
        // the UID of the user making the request, and that this corresponds with the
        // UID attempting to be written to the document in the update
        allow update: if requestMatchesUID() && resourceMatchesUID();
        // delete if the UID of the existing document requested to be deleted matches
        // the UID of the user making the request
        allow delete: if resourceMatchesUID();
    }

    // the user UID of the request matches the UID to be assigned to the data in 
    // the document
    function requestMatchesUID() {
        return request.auth.uid == request.resource.data.uid;
    }

    // the user UID of the request matches the UID of the existing document resource
    function resourceMatchesUID() {
        return request.auth.uid == resource.data.uid;
    }
  }
}

These rules specify that a user creating a “post” document can only assign a UID to the post that matches their unique user ID provided by Firebase Authentication. Also, a user can only update or delete a document if the UID of the existing document matches theirs.

This is a very simple example, but it illustrates how Firebase allows you to specify who can do what with your data, without needing to implement an entire backend with custom auth interceptors just to manage CRUD operations. We can simply specify this in our security rules, and Firebase will handle the rest. Now, having the following code in your frontend is acceptable:

import { initializeApp } from "firebase/app";
import { getFirestore, collection, addDoc } from "firebase/firestore";
import { getAuth } from "firebase/auth";

// TODO: replace the following with your app's Firebase project configuration
// See: https://support.google.com/firebase/answer/7015592
const firebaseConfig = {
  FIREBASE_CONFIG_HERE
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);

// Initialize Firestore and get a reference to the service
const db = getFirestore(app);

// Get the currently logged in user
// Refer to: https://firebase.google.com/docs/auth/web/manage-users
const auth = getAuth();
const user = auth.currentUser;

// Add a new document with a generated id.
const addDoc = async () => {
  const docRef = await addDoc(collection(db, "posts"), {
    title: "My first post!",
    content: "Hello world!",
    // ensure the uid in the data matches that of the 
    // authenticated user making the request
    uid: user.uid
});

What was previously completely discouraged, having database interactions live directly in your frontend, is now secure." No more hours spent on boilerplate code for simple CRUD operations, hours spent on implementing custom auth interceptors and maintenance of CI/CD pipelines for building and deploying backends. Firebase handles all of this for you, so you can focus on building your application.

Furthermore, there are no sensitive db credentials being stored in the frontend, and CRUD operations are hardly “sensitive business logic” we need to protect. For the super custom business logic, where we don’t want it living in the frontend and might want to have some more computational power, this is where Cloud Functions come to the rescue.

Cloud Functions

Let’s be real. 90% of applications are just CRUD operations. Only a small percentage is some complex, custom logic. For the CRUD operations, Firebase security rules are more than enough. But for the more complex computations, we can leverage Cloud Functions. Cloud functions allow us to set up a backend for our custom business logic by simply writing the logic in functions, and leaving it up to Firebase to handle the complexities around deploying, scaling and managing the infrastructure. An example of a cloud function might look as follows:

const functions = require('firebase-functions');
const admin = require('firebase-admin');

admin.initializeApp();

exports.notifyNewPost = functions.firestore
  .document('posts/{postId}')
  .onCreate((snap, context) => {
    // Get an object representing the new post document
    const newPost = snap.data();

    // Extract needed data. For instance, assuming each post has an 'authorId'
    const userId = newPost.authorId;
    const postId = context.params.postId;

    // Construct the email notification content. Here's a simple example:
    const emailContent = {
      to: userId,  // Assuming 'userId' is the user's email or a reference to fetch the email. You might want to change this as per your schema.
      subject: 'Your post has been published!',
      body: `Hello! Your post with ID ${postId} has been successfully created and published.`,
    };

    // Add this email content to the 'mail' collection (or wherever you handle email tasks in Firestore)
    return admin.firestore().collection('mail').add(emailContent);
  });

In this example, we listen for the event of a new “post” document being created in the Firestore database, and then create an email document in the “emails” collection to notify the user that their post has been published. This leverages on one of Firebase’s extensions that allows us to send emails by simply writing a document to a specified mail collection.

Creating and deploying these functions is as simple as running firebase init functions, creating a js file in functions/src to house the business logic and then running firebase deploy. What’s even cooler is that Firebase has a bunch of pre-baked events that we can leverage in our cloud functions. These events include things like documents being created, deleted or updated, user’s authenticating with the app and so much more. So now, we don’t need to worry about the overhead of setting up custom eventing pipelines either.

Trigger Options

You can even set up HTTP endpoints so your functions can be called in a fashion similar to a traditional REST API. This is super useful for things like webhooks.

Once you’ve written the logic, Firebase handles the deployment, scaling and infrastructure required by our functions without us having to worry about containerisation, container management, docker, Kubernetes management or any of the technical stuff.

Why does it not address framework independence and reusability?

The biggest down side of this approach is that if we integrate our calls to Firestore directly in our frontend and depend on Firebase triggers, we are now tied to Firebase as a framework. The business logic we implement for CRUD operations is also not reusable since it exists in a specific frontend implementation.

Suppose you started off building a web app in React. Down the line, you decide to work on an iOS app. In this case, all of the business logic and CRUD methods would have to be re-written in the language you choose to implement your app. Furthermoe, if you depend on Firebase triggers, any movement away from Firestore and Firebase Authentication would mean having to create and manage enirely custom eventing for your application.

Thankfully, with the advent of muli-platform frameworks like Flutter, this is becoming less of an issue. Flutter allows you to build web, mobile and desktop applications from a single code base. This means that you can write your business logic once, and then use it across all of your clients. This is a huge win for startups, and I think that Firebase paired with Flutter is a match made in heaven (more on this later).

To some extent, there is no escaping the inevitable refactor. Most applications outgrow their initial tech stack and require a refactor at some point or another. But Firebase allows you to get your product to market quickly, and then refactor when you need to. Heck, maybe you won’t make it past 10 users, then you’ll be giving yourself a pat on the back when you decided not to devote all your time and energy to creating the most sophiticated tech stack of all time. You’l be glad to have focused on your core value proposition and built quickly, without spending time on technical stuff.

What makes a startup unique?

“100% of startups that fail to get their first user, fail.” - Socrates

Startups have no time to waste solving imaginary problems (brilliant article speaking about “imaginary problems” here). Imaginary problems are exactly what they sound like: problems perceived to be important, but in reality, are not. In the context of a tech startup, they tend to express themselves in the form of fun technical challenges that us nerds like to work on, but don’t serve the real customer need given where you are in the phase of your development.

The context in which a startup operates

From everything I’ve read on the internet, every Y combinator video I’ve watched and blog post I’ve read, one thing is clear: it is the startups with the quickest feedback loops that win. The quicker you can get your product in front of users, the quicker you can learn what works and what doesn’t. Talk to as many of your customers and key industry stakeholders as possible, and then iterate on and improve your product by incorporating their feedback.

The quicker you iterate, incorporating research and feedback, the more likely you are to succeed.

An overly simplified view of this feedback loop is shown below:

Feedback Loop

The first step is establishing your user’s problem. What is it that you are trying to solve? After spending some time at the drawing board and ideating around how to solve this, you implement a proposed feature. You get this in the hands of your users as quickly as possible, and then you get feedback. You then take this feedback, and incorporate it into your next iteration. This process repeats until you (hopefully) have a product your users love.

YC has a pocket quide of essential advice for startups, but I want to draw attention to a few pieces of advice that really resonate with me:

  1. Do things that don’t scale
  2. Write code - talk to users
  3. Don’t scale your team/product until you have built something people want
  4. Pre-product market fit - do things that don’t scale: remain small/nimble
  5. Startups can only solve one problem well at any given time

What should startups optimise for

In keeping with the above, startups need to optimise for speed.

They should spend as little time as possible on anything that does not directly contribute to the feedback loop. This means spending little time on technical nitty gritties, and leveraging tools and technologies that allow you to build out products and features as quickly as possible.

Having super slick CI/CD pipelines, sophisticated backends that are capable of scaling to thousands, or even millions, of users and well-defined APIs with strict pipelines to ensure no breaking changes, all sound like cool problems we like to solve as nerdy developers. But at the end of the day, your user doesn’t care. They just wanna see the feature and see it before someone else beats you to it. In summary:

  1. “Startups can only solve one problem well at any given time”: you aren’t soliving the problem of having the most responsive APIs on the planet. Focus on your core value offering as a business and spend minimal time on technical overhead.
  2. “Do things that don’t scale”: who knows whether your idea will take off or not, so there’s no point investing in a future proof, scalable system (for both users of your product and developers in your team) without confirming you have a future proof idea
  3. Prototype quickly and get features in the hands of users, even if they’re not perfect. Your user’s will help you steer your product towards perfection. 😙🤌

Firebase is a great tool for startups

Firebase is great for startups because it allows you to focus on your core value proposition and build quickly. None of the technical fluff. All of the business value.

I’ll illustrate this by contrasting time I’ve spent on technicalities and how Firebase would have solved this:

  1. Time spent on setting up CI/CD pipelines to build and deploy backend microservices -> firebase deploy + Cloud Functions
  2. Time spent on setting up custom auth interceptors -> Firebase Authentication + Security Rules
  3. Time spent on boilerplate code for CRUD operations -> Firestore called directly from the frontend with Security Rules

There’s also a lot of time I’ve spend on managing our APIs and the CI/CD pipelines around this. We use Protocol Buffers and gRPC for defining and implementing APIs. This has its perks, but in the early stages when your APIs are constantly changing and evolving, not having to worry about breaking changes, or following a release procedure every time you want to make a change, is a huge win. Firebase allows you to simply write your business logic in Cloud Functions, read and write to your DB on the fly and not have to worry about breaking changes.

What does Firebase do?….

IT SAVES US DEVELOPMENT TIME!!!

So use it to your advantage. You’ll be glad you did.

Microservices meme

In the next post

In the next post we will look at our current setup and dive into more of the technical details around how it is implemented. Despite it being “overengineered” for the startup context, I still think it is a really cool tech stack that might offer something valuable to people working with this technology or eager to implemenet it. Looking forward to sharing it with you all.