Structuring a Firebase web project with Lerna

Bootstrapping an easy to manage structure for your Firebase web projects

João Teixeira
Firelayer
6 min readSep 30, 2019

--

When talking about a Firebase web project we tend to think about the simple single page application, maybe a sprinkle of React / Vue / Angular, add the Firebase SDK and off we go using Firestore, Authentication and all of those ready to use out-of-the-box goodies. While that’s the reality for most Firebase applications, some may need the help of a backend (Cloud Functions) or even to deploy multiple websites (Hosting) on the same project.

Often while dealing with these kind of projects a multi repository solution comes to mind, one repo for the frontend, one for the backoffice and one more for the backend, and it makes complete sense to have that separation of concerns, separate versioning and dependencies.

As a solo developer and even inside a team it’s easy to get lost in a multi repo project environment, it can become hard to track and manage changes. That’s were Lerna comes in, it can provide a somewhat multi repo experience in a single repository. Is going to help with the multiple packages management.

Goals for the Firebase project

Our simple example application will deploy:

  • 2 websites, landing website and backoffice (Hosting)
  • 1 backend (Cloud Functions for Firebase)

1. Setting up the project

Let’s start by creating the project with Lerna.

npm install --g lernamkdir firebase-structure && cd $_lerna init --independent

So we’ve created the project with Lerna in independent mode so that the versioning is independent for each package.

2. Firebase first steps 🔥

Register your project in Firebase https://console.firebase.google.com

Install firebase-tools and login to initialize our project locally:

npm install --g firebase-toolsfirebase loginfirebase init

For this example select the following options:

1. We will need to select Functions and Hosting.
2. Then select the Firebase project you would like to deploy.
3. For simplicity pick Javascript as your Cloud Functions language.
4. No to ESlint.
5. No to npm install dependencies now.
6. For Hosting leave the folder public for the time being
7. And select No for the single-page app

When finished you’ll probably have something like this:

After the firebase init command

3. Rearranging in different packages

Now let’s start creating the different 📦 packages for Lerna, for that we need to put the website and the functions inside the packages folder with their own package.json file.

mkdir packages/wwwmv public packages/www && mv functions packagesecho '{"name":"www","version":"0.0.0"}' > packages/www/package.json

Edit firebase.json with the new paths to reflect the new folder structure so that firebase knows were to find everything when firebase deploy is called.

firebase.json after the paths updated

We can now install all the packages dependencies with the Lerna bootstrap command

lerna bootstrap

…and deploy to check if everything is working

firebase deploy

If everything went right, you should see that the firebase hosting and functions were both deployed.

terminal output after the firebase deploy command successfully runs
Hosting and Functions both deployed.

4. Adding our second site to Hosting

Let’s go back to the Firebase console and on the “Hosting” dashboard we will find the button “Add another site” on the sections “Advanced”.

Click it and select the name for that website, a good strategy for naming could be <project name>-<scope>, for example: shop-blog, shop-www, shop-landing, shop-backoffice.

After the creation you will see that you now have two sites available in the dashboard.

Ok 👌! Now let’s get back to our code and see how we can deploy this site as well.

Create the site project folder with index.html and a package.json file.

mkdir packages/backoffice && mkdir packages/backoffice/publicecho '{"name":"backoffice","version":"0.0.0"}' > packages/backoffice/package.jsontouch packages/backoffice/public/index.html

We have our new site package aligned, let’s setup firebase to deploy it.

Configure the new hosting site with the command
firebase target:apply hosting alias resource-name
(see hosting multi-site documentation for more information)

firebase target:apply hosting backoffice fire-structure-backoffice
firebase target:apply hosting landing fir-structure-7bf07

You can retrieve the hosting resource names from the hosting dashboard:

Hosting target names.

We’ve configured the hosting target alias for each site in our projects, we can now set on the firebase.json the new hosting configurations:

And we’re ready to deploy again and we should see two different hosting URLs on the output.

firebase deploy

Cool, we now have our main structure with all three main packages separated.

Two hosting websites and cloud functions

With this kind folder structure it’s easy to have that separation of concerns. We can have the landing site with Gatsby and the backoffice with Vue without any problem, and a few ideas come to mind when using a multi package approach like this:

  • Single lint, build, test and release process.
  • Single place to report issues.
  • Easier to setup a development environment.

Just like Babel and many others have already testified.

Going further

5. Adding Express to our Cloud Functions

Like in many projects, we can really use an API to get our application going, so we’re starting one, in this case we will use Express to have our starting point.

First thing first, we add express package to functions.

lerna add express --scope=functions

This is equivalent of going to the folder packages/functions and running npm install express

Now we can add the express with our first endpoint.
Add the following code on packages/functions/index.js

Cloud Functions with the Express endpoint.

After we just need to deploy the functions and our API will be live and working, and we can do just that by saying that we just want to deploy the functions.

firebase deploy --only functions
Function deploy complete.

It will return the new Function URL so we can test it.
And voila, we have a starting point to build our API.

Remember

Cloud Functions are serverless, add only the dependencies on the functions package you are really going to use or the functions will be slower to start. This is referred as the cold start issue in the Cloud Functions docs:

“Functions are stateless, and the execution environment is often initialized from scratch, which is called a cold start. Cold starts can take significant amounts of time to complete. It is best practice to avoid unnecessary cold starts, and to streamline the cold start process to whatever extent possible (for example, by avoiding unnecessary dependencies).”

Final notes

It’s essential to have a structure that makes sense to work on, to be more productive and to ease the development process. The structure I’m presenting here may not be the one for you or your project, but, I hope it helps in your pursuit.

Firelayer — Launch your Firebase MVP 10x faster with our templates

Jump-start your Firebase Project with this open source project

https://firelayer.io

Thanks for reading. You can learn more about me on Medium and Github.

--

--