With our current setup, we have a single code project with a client and server directory. We are using git and GitHub so we are able to share code, create branches, and the like. So, there is not a good reason to create a new code project for production. It creates a lot of overhead we don't need.
First thing we need to do is create the new project in Firebase. Simple.
Now we need to let our client and server setup know about our new environment. We do this using the Firebase tools.
For clarity sake, let's say our current project is called "beefcake-staging" and we just created "beefcake-prod".
cd client
firebase use
# Displays a list of known Firebase projects and shows which project is currently active
# In our case we should see a single project entry
# * default (beefcake-staging)
# The "*" indicates active. `default` is an alias. `beefcake-staging` is the Firebase project name.
# To assign the active project we use its alias
firebase use default
# To add our newly created project
firebase use --add
# Select existing project
# Select `beefcake-prod`
# Set the alias to `prod`
cd ../server
firebase use --add
# Select the `beefcake-prod` project
# Set the alias to `prod`
So, why does this matter? When we use the firebase deploy
command, or other Firebase tool options for that matter, it will attempt to deploy our client to whatever the currently active project is. For Firebase, this is all we have to do in order to switch project context.
If we had an actual client application, we would have stored variables for how to connect to Firebase and any other service we are connecting to. So, now that we are moving to a new Firebase project, how to we accomodate that change in our code?
There is more than one answer, but let's keep it simple. Many times we need to hide keys and we don't want those keys stored in our code anyway. Instead, you would use a library like dotenv
.
project
L client
.env.staging
.env.prod
L server
L functions
.env.staging
.env.prod
Use dotenv
and create a .env.<environment>
file for each environment. When you load dotenv
you can dynamically specify the file, or, copy the appropriate env file to .env
in your build scripts.
require('dotenv').config({ path: `.env.${process.env.NODE_ENV}` })
Let's look at how we can make life easier by implementing some scripts in our package.json
files. First off, I want to launch a single script from the project root directory and have it run the appropriate scripts in the client
and server/functions
directories.
cd <project>
yarn init -y
yarn add -D npm-run-all
cd client
yarn add -D npm-run-all
cd ../server/functions
yarn add -D npm-run-all
Now for the scripts. When done, I will only need to run one command from the project directory to either run emulators, deploy to staging or deploy to production.
# Run emulators
yarn serve:local
# Deploy to staging
yarn deploy:staging
# Deploy to prod
yarn deploy:prod
NOTE: I'm only going to include the scripts portion of the package.json
files!
{
"name": "project",
"scripts": {
"serve:local": "npm-run-all --parallel client:local server:local",
"deploy:staging": "npm-run-all client:deploy:staging server:deploy:staging",
"deploy:prod": "npm-run-all client:deploy:prod server:deploy:prod",
"client:local": "cd client && yarn serve:local",
"client:deploy:staging": "cd client && yarn deploy:staging",
"client:deploy:prod": "ce client && yarn deploy:prod",
"server:local": "cd server/functions && yarn serve:local",
"server:deploy:staging": "cd server/functions && yarn deploy:staging",
"server:deploy:prod": "cd server/functions && yarn deploy:prod"
}
{
"name": "client",
"scripts": {
"serve:local": "npm-run-all use:staging copy-env:local start:local",
"deploy:staging": "npm-run-all use:staging copy-env:staging quasar:build:staging firebase:deploy:staging",
"deploy:prod": "npm-run-all use:prod copy-env:prod quasar:build:prod firebase:deploy:prod",
"use:staging": "firebase use default",
"use:prod": "firebase use prod",
"copy-env:local": "cp .env.local .env",
"copy-env:staging": "cp .env.staging .env",
"copy-env:prod": "cp .env.prod .env",
"start:local": "firebase emulators:start",
"firebase:deploy:staging": "firebase deploy -P default",
"firebase:deploy:prod": "firebase deploy -P prod"
}
{
"name": "server",
"scripts": {
"serve:local": "npm-run-all use:staging copy-env:local start:local",
"deploy:staging": "npm-run-all use:staging copy-env:staging firebase:deploy:staging",
"deploy:prod": "npm-run-all use:prod copy-env:prod firebase:deploy:prod",
"use:staging": "firebase use default",
"use:prod": "firebase use prod",
"copy-env:local": "cp .env.local .env",
"copy-env:staging": "cp .env.staging .env",
"copy-env:prod": "cp .env.prod .env",
"start:local": "firebase emulators:start",
"firebase:deploy:staging": "firebase deploy -P default",
"firebase:deploy:prod": "firebase deploy -P prod"
}