Plea and the Pledge: Truly Serverless

As modern application developers, we’re juggling many priorities: performance, flexibility, usability, security, reliability, and maintainability. On top of that, we’re handling dependencies, configuration, and deployment of multiple components in multiple environments and sometimes multiple repositories as well. And then we have to keep things secure and simple. Ah, the nightmare!

This is the reason we love serverless computing. Serverless allows developers to focus on the thing they like to do the most—development—and leave the rest of the attributes, including infrastructure and maintenance, to the platform offerings.

In this read, we’re going to see how Cloud Run and MongoDB come together to enable a completely serverless MEAN stack application development experience. We’ll learn how to build a serverless MEAN application with Cloud Run and MongoDB Atlas, the multi-cloud application data platform by MongoDB.

Containerized deployments with Cloud Run

All serverless platform offer exciting capabilities:

  • Event-driven function (not a hard requirement though)
  • No-infrastructure maintenance
  • Usage-based pricing
  • Auto-scaling capabilities

Cloud Run stands out of the league by enabling us to:

  • Package code in multiple stateless containers that are request-aware and invoke it via HTTP requests
  • Only be charged for the exact resources you use
  • Support any programming language or any operating system library of your choice, or any binary

Check this link for more features in full context.

However, many serverless models overlook the fact that traditional databases are not managed. You need to manually provision infrastructure (vertical scaling) or add more servers (horizontal scaling) to scale the database. This introduces a bottleneck in your serverless architecture and can lead to performance issues.

Deploy a serverless database with MongoDB Atlas

MongoDB launched serverless instances, a new fully managed, serverless database deployment in Atlas to solve this problem. With serverless instances you never have to think about infrastructure — simply deploy your database and it will scale up and down seamlessly based on demand — requiring no hands-on management. And the best part, you will only be charged for the operations you run. To make our architecture truly serverless, we’ll combine Cloud Run and MongoDB Atlas capabilities.

What’s the MEAN stack?

The MEAN stack is a technology stack for building full-stack web applications entirely with JavaScript and JSON. The MEAN stack is composed of four main components—MongoDB, Express, Angular, and Node.js.

  • MongoDB is responsible for data storage.
  • Express.js is a Node.js web application framework for building APIs.
  • Angular is a client-side JavaScript platform.
  • Node.js is a server-side JavaScript runtime environment. The server uses the MongoDB Node.js driver to connect to the database and retrieve and store data.

 

Steps for deploying truly serverless MEAN stack apps with Cloud Run and MongoDB

In the following sections, we’ll provision a new MongoDB serverless instance, connect a MEAN stack web application to it, and finally, deploy the application to Cloud Run.

1. Create the database

Before you begin, get started with MongoDB Atlas on Google Cloud.

 

Once you sign up, click the “Build a Database” button to create a new serverless instance. Select the following configuration:

 

Once your serverless instance is provisioned, you should see it up and running.

 

Click on the “Connect” button to add a connection IP address and a database user.

For this blog post, we’ll use the “Allow Access from Anywhere” setting. MongoDB Atlas comes with a set of security and access features. You can learn more about them in the security features documentation article.

Use credentials of your choice for the database username and password. Once these steps are complete, you should see the following:

 

Proceed by clicking on the “Choose a connection method” button and then selecting “Connect your application”.

 

Copy the connection string you see and replace the password with your own. We’ll use that string to connect to our database in the following sections.

2. Set up a Cloud Run project

First, sign in to Cloud Console, create a new project, or reuse an existing one.

Remember the Project Id for the project you created. Below is an image from https://codelabs.developers.google.com/codelabs/cloud-run-hello#1 that shows how to create a new project in Google Cloud.

 

Then, enable Cloud Run API from Cloud Shell:

1. Activate Cloud Shell from the Cloud Console. Simply click Activate Cloud Shell.

 

 

2. Use the below command:
gcloud services enable run.googleapis.com

 

We will be using Cloud Shell and Cloud Shell Editor for code references. To access Cloud Shell Editor, click Open Editor from the Cloud Shell Terminal:

 

 

Finally, we need to clone the MEAN stack project we’ll be deploying.

 

We’ll deploy an employee management web application. The REST API is built with Express and Node.js; the web interface, with Angular; and the data will be stored in the MongoDB Atlas instance we created earlier.

Clone the project repository by executing the following command in the Cloud Shell Terminal:

git clone https://github.com/mongodb-developer/mean-stack-example.git

 

In the following sections, we will deploy a couple of services—one for the Express REST API and one for the Angular web application.

3. Deploy the Express and Node.js REST API

First, we’ll deploy a Cloud Run service for the Express REST API.

The most important file for our deployment is the Docker configuration file. Let’s take a look at it:

mean-stack-example/server/Dockerfile

 

# Use the official lightweight Node.js 12 image.
# https://hub.docker.com/_/node
FROM node:17-slim

WORKDIR /usr/app
COPY ./ /usr/app

# Install dependencies and build the project.
RUN npm install
RUN npm run build

# Run the web service on container startup.
CMD ["node", "dist/server.js"]

 

The configuration sets up Node.js, and copies and builds the project. When the container starts, the command “node dist/server.js” starts the service.

 

To start a new Cloud Run deployment, click on the Cloud Run icon on the left sidebar:

 

Then, click on the Deploy to Cloud Run icon:

 

Fill in the service configuration as follows:

– Service name: node-express-api
– Deployment platform: Cloud Run (fully managed)
– Region: Select a region close to your database region to reduce latency
– Authentication: Allow unauthenticated invocations

Under Revision Settings, click on Show Advanced Settings to expand them:

– Container port: 5200
– Environment variables. Add the following key-value pair and make sure you add the connection string for your own MongoDB Atlas deployment:

 

ATLAS_URI:mongodb+srv:/<username>:<password>@sandbox.pv0l7.mongodb.net/meanStackExample?retryWrites=true&w=majority

For the Build environment, select Cloud Build.

Finally, in the Build Settings section, select:

 

– Builder: Docker
– Docker: mean-stack-example/server/Dockerfile

 

Click the Deploy button and then Show Detailed Logs to follow the deployment of your first Cloud Run service!

After the build has completed, you should see the URL of the deployed service:

 

 

 

Open the URL and append ‘/employees’ to the end. You should see an empty array because currently, there are no documents in the database. Let’s deploy the user interface so we can add some!

4. Deploy the Angular web application

Our Angular application is in the client directory. To deploy it, we’ll use the Nginx server and Docker.

> Just a thought, there is also an option to use Firebase Hosting for your Angular application deployment as you can serve your content to a CDN (content delivery network) directly.

Let’s take a look at the configuration files:

mean-stack-example/client/nginx.conf

 

events{}

http {

   include /etc/nginx/mime.types;

   server {
       listen 8080;
       server_name 0.0.0.0;
       root /usr/share/nginx/html;
       index index.html;

       location / {
           try_files $uri $uri/ /index.html;
       }
   }
}

 

In the Nginx configuration, we specify the default port—8080, and the starting file—index.html.

mean-stack-example/client/Dockerfile

 

FROM node:17-slim AS build

WORKDIR /usr/src/app
COPY package.json package-lock.json ./

# Install dependencies and copy them to the container
RUN npm install
COPY . .

# Build the Angular application for production
RUN npm run build --prod

# Configure the nginx web server
FROM nginx:1.17.1-alpine
COPY nginx.conf /etc/nginx/nginx.conf
COPY --from=build /usr/src/app/dist/client /usr/share/nginx/html

# Run the web service on container startup.
CMD ["nginx", "-g", "daemon off;"]

 

In the Docker configuration, we install Node.js dependencies and build the project. Then, we copy the built files to the container, configure, and start the Nginx service.

Finally, we need to configure the URL to the REST API so that our client application can send requests to it. Since we’re only using the URL in a single file in the project, we’ll hardcode the URL. Alternatively, you can attach the environment variable to the window object and access it from there.

mean-stack-example/client/src/app/employee.service.ts

 


@Injectable({
 providedIn: 'root'
})
export class EmployeeService {
 // Replace with the URL of your REST API
 private url = 'https://node-express-api-vsktparjta-uc.a.run.app'; 

 

We’re ready to deploy to Cloud Run! Start a new deployment with the following configuration settings:

– Service Settings: Create a service
– Service name: angular-web-app
– Deployment platform: Cloud Run (fully managed)
– Authentication: Allow unauthenticated invocations

 

For the Build environment, select Cloud Build.

Finally, in the Build Settings section, select:

– Builder: Docker
– Docker: mean-stack-example/client/Dockerfile

 

Click that Deploy button again and watch the logs as your app is shipped to the cloud! When the deployment is complete, you should see the URL for the client app:

 

Open the URL, and play with your application!

 

Command shell alternative for build and deploy

The steps covered above can alternatively be implemented from Command Shell as below:

1. Create the new project directory named “mean-stack-example” either from the Code Editor or Cloud Shell Command (Terminal):

mkdir mean-stack-demo 
cd mean-stack-demo

 

2. Clone project repo and make necessary changes in the configuration and variables, same as mentioned in the previous section.

 

3. Build your container image using Cloud build by running the command in Cloud Shell:

gcloud builds submit –tag gcr.io/$GOOGLE_CLOUD_PROJECT/mean-stack-demo

 

$GOOGLE_CLOUD_PROJECT is an environment variable containing your Google Cloud project ID when running in Cloud Shell.

 

4. Test it locally by running:

docker run -d -p 8080:8080 gcr.io/$GOOGLE_CLOUD_PROJECT/mean-stack-demo

and by clicking Web Preview, Preview on port 8080.

 

5. Run the following command to deploy your containerized app to Cloud Run:

gcloud run deploy mean-stack-demo –image gcr.io/$GOOGLE_CLOUD_PROJECT/mean-stack-demo –platform managed –region us-central1 –allow-unauthenticated –update-env-vars DBHOST=$DB_HOST

a. –allow-unauthenticated will let the service be reached without authentication.

b. –platform-managed means you are requesting the fully managed environment and not the Kubernetes one via Anthos.

c. –update-env-vars expects the MongoDB Connection String to be passed on to the environment variable DBHOST.

Hang on until the section on Env variable and Docker for Continuous Deployment for Secrets and Connection URI management.

d. When the deployment is done, you should see the deployed service URL in the command line.

e. When you hit the service URL, you should see your web page on the browser and the logs in the Cloud Logging Logs Explorer page.

 

5. Environment variables and Docker for continuous deployment

If you’re looking to automate the process of building and deploying across multiple containers, services, or components, storing these configurations in the repo is not only cumbersome but also a security threat.

 

1. For ease of cross-environment continuous deployment and to avoid security vulnerabilities caused by leaking credential information, we can choose to pass variables at build/deploy/up time.

update-env-vars allows you to set the environment variable to a value that is passed only at run time. In our example, the variable DBHOST is assigned the value of $DB_HOST. which is set as DB_HOST = ‘<<ENCODED CONNECTION URI>>’.

Please note that unencoded symbols in Connection URI (username, password) will result in connection issues with MongoDB. For example, if you have a $ in the password or username, replace it with %24 in the encoded Connection URI.

 

2. Alternatively, you can also pass configuration variables as env variables at build time into docker-compose (docker-compose.yml). By passing configuration variables and credentials, we avoid credential leakage and automate deployment securely and continuously across multiple environments, users, and applications.

Conclusion

MongoDB Atlas with Cloud Run makes for a truly serverless MEAN stack solution, and for those looking to build an application with a serverless option to run in a stateless container, Cloud Run is your best bet.

Before you go…

Now that you have learnt how to deploy a simple MEAN stack application on Cloud Run and MongoDB Atlas, why don’t you take it one step further with your favorite client-server use case? Reference the below resources for more inspiration:

  1. Cloud Run HelloWorld: https://codelabs.developers.google.com/codelabs/cloud-run-hello#4
  2. MongoDB – MEAN Stack: https://www.mongodb.com/languages/mean-stack-tutorial

If you have any comments or questions, feel free to reach out to us online: Abirami Sukumaran and Stanimira Vlaeva.

 

 

By: Stanimira Vlaeva (Developer Advocate, MongoDB) and Abirami Sukumaran (Developer Advocate, Google)
Source: Google Cloud Blog

Previous Announcing General Availability Of ReCAPTCHA Enterprise Password Leak Detection
Next Measuring Climate And Land Changes With AI