How to use Firebase Cloud Functions and Yarn Workspaces

Santi M.A.
Google for Developers Europe
6 min readOct 1, 2019

--

🇪🇸 Como usar Firebase Cloud Functions y Yarn Workspaces

Hi! 👋🏻

I started working on a side project and when I tried to share code between my frontend(Nuxt.js) and backend(Firebase Cloud Functions) I found a problem 😱.

Initial idea 💡

When I finish reading Clean Architecture by Uncle Bob I wanted to implement a “clean architecture” myself to try all the concepts Uncle Bob explain.

To achieve this architecture I decided to use yarn workspaces and lerna to setup a mono-repo with some sub-packages, one or more with the business rules.

“This way of organizing the code will allow me to share code between Firebase Cloud Functions and Nuxt.js” or that’s what I thought….

The problem 🤦🏻‍♂️

How can I say this… at the moment the Firebase CLI doesn’t support mono-repos, it has an open issue about this.

My solution 😇

If you take a look at the open issue you can see some complex ideas, IMHO, since I think the easy way is to publish the packages into a registry to be able to install them when the Firebase Cloud Functions are being deployed.

Instead of using a public registry like npm, or their private option 💸, in this post, we are going to use GitHub Package Registry. At the moment the GitHub Registry is in beta but it allows private packages and a new tab to have the packages linked with the repository.

The example 👀

Let’s create two packages: one with our business rules and other with the Cloud Functions code.

Configure the mono-repo

To start we are going to create a new folder for this example, inside it we are going to use lerna to init our mono-repo.

npx lerna init

Once lerna finishes the init process we can find some files and folder into our project folder.

packages/

Inside this folder, we are going to have all our sub-packages, each with their own folder, package.json, code, dependencies, tests…

package.json

This is our main package.json, in this file we only have lerna as a dev dependency.

To enable yarn workspaces we have to add the property “workspaces” and configure into which folder we have our sub-packages, in this example, we are going to use the packages folder that lerna had to create for us. Also, we have to mark the package as private, otherwise the yarn workspaces won’t work.

{  
"name": "root",
"private": true,
"workspaces": {
"packages": [
"packages/**"
]
},
"devDependencies": {
"lerna": "^3.16.4"
}
}

lerna.json

Here we need to change a bit the default lerna configuration in order to use yarn as a package manager and enable yarn workspaces.

{
"packages": ["packages/*"],
"version": "1.0.0",
"npmClient": "yarn",
"useWorkspaces": true
}

Note: We can init this project without lerna, but if you take a look at the final code you will see that we use lerna to run commands in all the sub-packages at once.

Configure our first sub-package

Since I’m watching Breaking Bad again I’m going to create a package called: “@santima10/say-my-name”.

Note: with GitHub Package Registry, we have to add our GitHub username as the package scope since GitHub only support scoped packages at the moment.

You can found all the package code in the example repo. I build this package with TypeScript and Jest if you have any doubt you can reach me on Twitter.

Publishing our package to GitHub Package Registry

First, we need to logging in to GitHub Package Registry using a Personal Access Token with some scopes: repo, write:packages and read:packages.

When we have the token, we can continue by login in our package manager, I recommend to use npm if you want to store your password or yarn if you want to be asked for your password every time you publish a new version:

npm login --registry=https://npm.pkg.github.com/<your-github-username>

Note: when npm (or yarn) ask us for a password we have to paste the GitHub token, not your GitHub password.

If everything goes as we expected and we log in with success, we only have to add this new config to the package.json in the “@santima10/say-my-name” package:

“publishConfig”: {
“registry”: “https://npm.pkg.github.com/"
}

With this additional configuration, we set the GitHub Package Registry as the registry where we want to publish our package, the default one is npm.

Now we can publish our package 🚀.

yarn publish // npm publish

Add “Firebase Cloud Functions” to the project

To add the Firebase Cloud Functions we can use the Firebase CLI as we would do with a “normal project”.

firebase init functions

We can choose any configuration, but I will use TypeScript + TSLint. I recommend not to install any dependency yet.

Once we have the functions folder we must move it to the packages folder, since we are going to have the Firebase Cloud Functions as another package inside our repo.

To let Firebase know where the code of our functions is, we have to update the firebase.json file:

{
"functions": {
"source": "packages/functions",
// ...
}
}

Before we can install the Cloud Functions dependencies with yarn we have to update our main package.json file:

//...
"workspaces": {
"packages": [
"packages/**"
],
"nohoist": [ // <-------
"**/firebase-admin",
"**/firebase-admin/**",
"**/firebase-functions",
"**/firebase-functions/**"
]
},//...

The nohosit property tells yarn to install the firebase-admin and firebase-functions dependencies as local dependencies into each sub-package instead of as global dependencies with symlinks in each sub-package.

Also, we have to add a version and our “@santima10/say-my-name” dependency into the package.json file in the packages/functions folder.

Note: without version, yarn workspaces ignores the package :D

{
"name": "@santima10/functions",
"version": "1.0.0", // Firebase CLI doesn't add a version
"scripts": {
"build": "tsc",
"predeploy": "yarn build",
"deploy": "firebase deploy --only functions",
},
"engines": {
"node": "10"
},
"main": "build/index.js", // I updated the TSC config to have build as outDir
"dependencies": {
"firebase-admin": "^8.0.0",
"firebase-functions": "^3.1.0",
"@santima10/say-my-name": "1.0.0" // here is our dependency
}, "devDependencies": {
"tslint": "^5.12.0",
"typescript": "^3.2.2"
},
"private": true
}

Now we can use our dependency inside the Cloud Functions:

import * as functions from "firebase-functions";
import { sayMyName as sayMyNameFn } from "@santima10/say-my-name";
export const sayMyName = functions.https.onRequest((_, response) => {
return response.send(sayMyNameFn());
});

Deploy the “Firebase Cloud Functions”

To allow the Firebase environment to access the GitHub Package Registry we have to add a .npmrc inside the functions directory with some configuration:

//npm.pkg.github.com/:_authToken=<github-access-token>
@<your-github-username>:registry=https://npm.pkg.github.com/<your-github-username>

The first line in this file tells npm (or the package manager Firebase uses) how to authorize to the GitHub Registry.

@santima10:registry=https://npm.pkg.github.com/santima10

The second line, tells the package manager that all the packages within the “@santima10” scope are in the https://npm.pkg.github.com/santima10 registry instead of the default one.

Note: you can re-utilize the GitHub token we use to publish the package or create a new one with just permission to read the packages.

And… we are ready to deploy our Firebase Cloud Functions 🚀.

firebase deploy --only functions

If you like this post 😍 or you have any doubts 🤔 you can reach me on Twitter or when I’m streaming on Twitch 📺. Also, you can visit my web 🌍 where you can found all my other posts, contact, and projects.

--

--

Santi M.A.
Google for Developers Europe

Software Engineer 🚀 · @asturiasjs 📅 · “live coding” en https://santi.live 🟣 · Jugador de pachangas 🏀 · y de videojuegos 🎮 · Lego 🏗