Deploying a NodeJS Application on GCP with App Engine & CloudBuild (Part 2)

Code, Meet the Cloud.

Will Mundy
RiceApps
5 min readMay 18, 2020

--

This three-part tutorial will walk you through all the steps you need to deploy your NodeJS application to Google App Engine, replete with build triggers from Github and environment variables as the sprinkles on top. This is the third and final installment of the series — the first part is found here, and the third part is found here.

In the first part of the series, we set up App Engine and Cloud Build. Currently, Cloud Build is connected to our Github repositories and we have triggers enabled that will deploy our app once a certain branch ( gcp deploy ) is “pushed” to, or receives an update. But we still need to define in our code repository how Cloud Build can actually build our application — that is, what steps it needs to take to build it. Additionally, App Engine needs to set certain options so that it can successfully run both our frontend and backend on the same application.

So let’s get started!

Code, Meet the Cloud.

Open both your frontend and backend code repositories as separate windows inside your favorite editor. In each, we’ll need to create both app.yaml and cloudbuild.yaml files. The app.yaml file will specify the settings for the App Engine instance we created, while the cloudbuild.yaml file will dictate commands our previously created triggers will need to run in order to successfully build our app.

We’ll start with the frontend.

Frontend

In the root of your frontend directory, create a file called client.yaml, this will serve as the frontend’s app.yaml file.

This tutorial will assume that your application is compiled into a dist/ folder, although if the folder name is different then feel free to change the associated code. For more information on what a dist/ folder is, check out this helpful StackOverflow post. If your application does not run from a dist/ folder, you will need to make some modifications to both the client.yaml and cloudbuild.yaml files, but the overall process should be the same.

Paste the following into your client.yaml file.

Next, create a file called cloudbuild.yaml, also in the root of your frontend directory. Paste in the following code:

Let’s quickly walk through what this code does. At a high level, this outlines the “steps” that Google Cloud Build will use to deploy your app.

The steps, in (sorta) English:

  • Run npm install to install any necessary packages.
  • Run npm run build to create the dist/ folder.
  • Deploys your application to Google App Engine with the “version” of the application corresponding to the _GAE_VERSION environment variable we set in the trigger.
  • Adjusts the traffic for our application according to the _GAE_TRAFFIC environment variable we set in the trigger.

For reference, your directory structure should look a little like this. Note how client.yaml and cloudbuild.yaml are both at the same level as package.json, and outside of the src/ folder.

Next, we’ll setup the code for the backend.

Backend

In the root of your backend directory, create a file called api.yaml, this will serve as the backend’s app.yaml file. Just like before, paste the following into this file:

Since our backend is simply serving as an API, we don’t need any handlers for it. However, since we want this backend to run on the same instance of GAE as our frontend, we must differentiate the service. Thus, we specify the service as “api” here, rather than use “default” as we did for the frontend.

Next, create a file called cloudbuild.yaml, also in the root of your backend directory. Paste in the following code:

The steps, in English:

  • Again, run npm install
  • This runs a special npm command that we will write ourselves. In terminal, it would look like this: MY_ENV_VAR=${_MY_ENV_VAR} npm run create-env (with ${_MY_ENV_VAR} replaced with an actual value by Google Cloud Build, as specified in the “Substitution Variables” section for the trigger)
  • Finally, the application is deployed to Google App Engine with the “version” of the application corresponding to the _GAE_VERSION environment variable we set in the trigger. Notably, because we differentiated the service variable in the api.yaml file, this deployment will not overwrite the frontend.

What’s with the create-env?

Right. Above, we’ve seen how easy it is to pass environment variables from Google Cloud Build triggers to the cloudbuild.yaml file; however, we need to do a few extra steps in order to pass custom environment variables from the triggers to inside our NodeJS backend itself. But once these steps are completed, we can add as many environment variables as we’d like. This not only protects sensitive information, such as credentials, but also allows us to store different values for different triggers — thus opening the door to different environments of our application.

The changes to enable this functionality are short and sweet. First, we’ll head to our package.json and navigate to the “scripts” section, adding in this short command below:

This simply takes the environment variables we specify in our cloudbuild.yaml (step 2) and puts them into a .env file. In this case, we will just be storing MY_ENV_VAR in this .env file.

A closeup shot inside the cloudbuild.yaml file where we set our environment variables.

Next, we’ll need to run npm install dotenv -s , which will add the dotenv package to your application. This package allows us to actually use the .env file.

The last step is to make the environment variables in our .env file available to the rest of the project. To do this, we must create a config.js file (this can go wherever you’d like, such as the src folder). Then paste in the following:

Great. Now your environment variables can be used anywhere in your code, as long as you import this config.js file! Credit to this article for help on this method.

Quick Sanity Check

Check that the root of your directory now has two new files, api.yaml and cloudbuild.yaml. It should look similar to this:

Now that our frontend and backend are ready, it’s time for the final step — running our triggers!

We’ll encounter some issues along the way, but we’ll handle them all in the next part, located here. See you in the next part of the series!

--

--