Getting started React App with Firebase Web and Cloud Run in GCP

Elina Valieva
Google Cloud - Community
4 min readJan 2, 2023

This is a tutorial on how to start developing and deploying a simple React App in GCP. I would not focus on profs and confs of using Firebase and other approaches, just on how to start and deploy the app from the scratch and set up CI. This app is used for sharing some posts with:

  • Google Authorization
  • Uploading and storing images
  • Saving data in the database
  • Hosting and deployment

With Firebase Web we could easily start our application from scratch, we just need to create a project and register the application, save generated config in a file:

import {addDoc, collection, doc, getDoc, getFirestore, limit, onSnapshot, orderBy, query} from "firebase/firestore";
import {initializeApp} from "firebase/app";
import {getDownloadURL, getStorage, ref, uploadBytes} from "firebase/storage";
import {getAuth, GoogleAuthProvider, signInWithPopup} from "firebase/auth";

// TODO: Replace the following with your app's Firebase project configuration
const firebaseConfig = {
...
};

const app = initializeApp(firebaseConfig);

const db = getFirestore(app); // Used for storing data
const storage = getStorage(); // Used for storing images
const auth = getAuth(app); // Used for authentification

Sample of code for authentication, uploading images, and saving and retrieving data from the database below:

export const FirebaseService = {

getPostById: async function (id) {
const querySnapshot = await getDoc(doc(db, 'tourest', id));
let fetchedItem = querySnapshot.data();
return fetchedItem;
},

addPost: async function (data) {
return await addDoc(collection(db, 'tourest'), data);
},

uploadImage: function (image) {
const storageRef = ref(storage, `/files/${image.name}`)
return uploadBytes(storageRef, image).then((r) => {
return getDownloadURL(r.ref)
});
}
}

export const signWithGoogle = () => {
signInWithPopup(auth, provider).then((result) => {
// proceed authentification
}).catch((error) => console.log(error))
}

Architecture Diagram

For deployment, we use Cloud Build which triggers build for new Docker image and redeployment for Cloud Run and updating content for Firebase Hosting.

The application itself uses Firebase Authorization with a Google account to prevent unauthorized post creation, Firebase Storage for storing images, and Firestore as a database for storing posts and link references from Storage.

From a hosting perspective, we could use Firebase Hosting or Cloud Run itself. I would not focus on which approach in hosting is better.

Architecture diagram

Deploying the system

Prerequisites for deployment:

  • Google Cloud Project with a configured billing account
  • Terraform
  • Project repository on Cloud Source Repositories

From a project initializing perspective, we are using Terraform, which allows us to set up all required infrastructure for the application like Firebase Web, Cloud Build, IAM, etc. The small script below init.sh wraps all terraform commands and fills all mandatory terraform variables. By default project, zone, and region are filled from the default GCP config, but you could also override them if needed.

project=`gcloud config list --format 'value(core.project)' 2>/dev/null`
if ! [ -z "$project" ]; then
echo "Would you like to override $project: y/n"
read flag
fi
# Read project name
if [[ -z "$project" || "$flag" == "y" ]]; then
echo "Enter project name: "
read project
fi
echo "TF_VAR_PROJECT=$project"
export TF_VAR_PROJECT_ID="$project"
...
# Terraform deployment
terraform init
terraform plan
terraform apply -auto-approve

For deploying to Cloud Run we need to set up Dockerfile by wrapping the app to Nginx and creating a config file with updated environment variables.

# Build Frontend Assets
FROM node:18-alpine as build

...

WORKDIR /app
## Build sources
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Serve Frontend Assets
FROM fholzer/nginx-brotli:v1.12.2

WORKDIR /etc/nginx
ADD nginx.conf /etc/nginx/nginx.conf

COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 443
CMD ["nginx", "-g", "daemon off;"]

For firebase hosting, we need to define firebase.conf file with sample configuration below:

{
"hosting": {
"public": "build",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
...
"headers": [{
"source" : "**",
"headers" : [
{
"key" : "Cache-Control",
"value" : "max-age=31536000"
}
]
}]
}
}

For automating our deployment process we are using Cloud Build, which allows us on Git push and pull requests to produce a Docker container, deploy Cloud Run with a new image version, and update sources for Firebase hosting.

steps:
- id: 'build image'
name: 'gcr.io/cloud-builders/docker'
args: [ 'build',
'-t', 'gcr.io/${_PROJECT}/github.com/${_ACCOUNT}/${_IMAGE}:$SHORT_SHA',
'--build-arg', 'API_KEY=${_FIREBASE_API_KEY}',
'--build-arg', 'APP_NAME=${_FIREBASE_APP_ID}',
'--build-arg', 'GCP_PROJECT=${_PROJECT}',
'--build-arg', 'MESSAGING_SENDER_ID=${_FIREBASE_MESSAGING_SENDER_ID}',
'.' ]

- id: 'push image to artifactory'
name: 'gcr.io/cloud-builders/docker'
args: [ 'push', 'gcr.io/${_PROJECT}/github.com/${_ACCOUNT}/${_IMAGE}:$SHORT_SHA' ]

- id: 'cloud run deploy'
name: 'gcr.io/cloud-builders/gcloud'
args: [ 'run', 'deploy', '${_APP}',
'--region=${_REGION}',
'--port=443',
'--allow-unauthenticated',
'--platform=managed',
'--image=gcr.io/${_PROJECT}/github.com/${_ACCOUNT}/${_IMAGE}:$SHORT_SHA' ]

...

- id: 'firebase hosting'
name: 'gcr.io/${_PROJECT}/firebase'
args: [ 'deploy', '--project=${_PROJECT}', '--only=hosting' ]

All variables above from cloudbuild.yaml file defined in Cloud Build in GCP.

Conclusion

Leveraging technology like firebase web makes it extremely easy to start development simple React with authentication, hosting, storage, and database usage. From a deployment perspective, you could host directly in Firebase or use Cloud Run.

GitHub: https://github.com/ElinaValieva/tourest_gcp

--

--