MongoDB, Express, React and Node.js on the AppCloud with Docker

MERN_at_Swisscom_Application_Cloud

Hi there! Since you are reading this, chances are that you are interested in building and deploying isomorphic/universal web applications on the AppCloud or you would like to know more about the underlying technologies and techniques. If that’s the case then you are on the right spot. In the next few paragraphs, I will show you how to:

  • Quickly bootstrap an isomorphic/universal web app
  • Wrap it into a Docker container without coupling the application to Cloud Foundry VCAP_SERVICES

  • Deploy it on the AppCloud

Let us start then …

Bootstrap an Isomorphic/Universal Web App

In order to quickly bootstrap one such application, I will be using MERN a command line, scaffolding tool which uses  MongoDB, Express, React, and NodeJS (among other tools & libraries) to get you started .

In case you do not know MERN, here are the 6 steps you need to get you started. The final result of these steps will be a simple blogging platform.

  1. Install MongoDB (or via Homebrew, in case you are a Mac user) and start it; make sure MongoDB listens on its default port 27017
  2. Install NodeJS (or via NVM, recommended for frequent NodeJS users)
  3. Install MERN command line interface via:
    npm install -g mern-cli
  4. Create your blog
    mern <YourApp> && cd <YourApp>
  5. Install Node.js packages
    npm install
  6. Start the app
    npm start

After these steps you should be able to access the app via your favourite browser on: http://localhost:8000

Containerizing Your App with Docker

When developing an application for the AppCloud sooner or later you will want to consume some services (such as MongoDB, RabbitMQ, etc.). When this happens you will essentially face the following question:

How do I connect to this service?

As you probably know, AppCloud is an instance of Cloud Foundry and therefore they (often) share the same documentation. Starting a cloud service (AppCloud docs / Cloud Foundry docs) and binding it to an application (AppCloud docs / Cloud Foundry docs) is pretty well explain in the docs. When using the AppCloud, you can also create and bind services via its UI.

What happens once you have bound a service to an application is that Cloud Foundry (CF) provides a VCAP_SERVICES environmental variable to be used by the app at runtime. This variable contains information (in a JSON format) on how to connect to your service. Here is what it looks like:

An example of VCAP_SERVICES

Figure 1 – An Example of VCAP_SERVICES environment variable with a MongoDB service. What we need to connect to MongoDB is only the URI

Reading further through the docs you will come across on two approaches for using the VCAP_SERVICES. Here is a summary of each of them:

  • Develop some logic in your application which parses & makes use of VCAP_SERVICES – the approach requires that you write some non-business oriented code for treating Cloud Foundry specifics which kind of cements your application to Cloud Foundry. What if one day you decide to move to something else?
  • Auto-configuration through use of buildpacks – here the guys from CF have decided to be somewhat obscure. What is auto-configuration and how do you actually use it? Marching through the docs, you will witness how auto-configuration gets renamed to “auto-reconfiguration”. As it turns out, to make your app auto-re/configurable you still need to change the code of your application. For Java-based apps this means you need an external dependency which parses VCAP_SERVICES for you and injects the result in your code. For JavaScript apps running on NodeJS the CF guys directly suggest you actually do the parsing manually. Which brings us to the 1st approach.

As a summary, both approaches lead to the same end – tying up your app to Cloud Foundry.

The good news is that we can do better. Looking at the example VCAP_SERVICE on Fig.1, what we want (and need) in order to connect to MongoDB is the URI. As a reminder, the MERN-based project from the previous blog had the following structure:

Basic MERN project structure

Figure 2 – MERN-based Project Structure; “config.js” contains the needed information for connecting to services

Inside the “config.js” file there is the following line:

mongoURL: process.env.MONGO_URL || 'mongodb://localhost:27017/mern-starter',

which basically states that if there is a MONGO_URL environment variable then take the URI from there or else use the default one. So, if we manage to extract the URI from the VCAP_SERVICES and put it in a MONGO_URL environment variable then our problem is solved.

We can tackle this in at least two ways, both of which do not involve any dependencies on Cloud Foundry:

  • Manually extract and setup the environment variable – this could be done via the AppCloud UI “Set Environment Variable” feature or via a command line and/or Application manifest (for more on this manifest see the docs). Keep in mind though that if the service binding changes you will have to manually update the environment variable
  • Build a Docker container which holds a logic for extracting the MongoDB URI from VCAP_SERVICES and exporting it as an environment variable.

First approach has the drawback of a manual work which needs to be performed each time you deploy an app (and therefore does not really scale) and the benefits of being relatively simple and straightforward. The second approach has the reversed characteristics. It’s reusable but it requires some investment in choosing an appropriate tool and setting up the Docker image. This post is designed to resolve the negative side of the second approach, namely giving you a how-to of building such a reusable Docker image.

The below solution revolves around “a lightweight and flexible command-line JSON processor” called jq. This tool allows you to convert the content of VCAP_SERVICES variable into this:

mongodb://VEbIr54kZvLo4Pdm:zpZwwDKGbJJOgI8@e769ym1zfw2ob6.service.consul:3264/Akq0r3T5N8n3zHJ

by executing the following command:

echo $(echo $VCAP_SERVICES | jq '.mongodb[0].credentials.uri') | cut -d "\"" -f 2

Knowing that, what we need is to build an executable bash script (I’ve called it start.sh), with this content:

Docker start bash script

Figure 3 – Content of start.sh – an entry point for the Docker image

The file is then placed inside the MERN-based project, as shown in Fig. 4 below:

MERN project structure after adding the start bash script

Figure 4 – Overview of Project Structure

Once you have that the only piece of the puzzle is the content of the Dockerfile, which is as follows:

Dockerfile content

Figure 5 – Content of Dockerfile

Building & Uploading Docker Image to AppCloud

Now that we have the project ready for running and deploying, here is how to proceed:

  1. Open a terminal and go to the directory containing the Dockerfile
  2. Build the Docker image by running:
    docker build -t swd/mern:1.0 .​
  3.  List the Docker images on your machine: 
    docker images​
  4. Tag the image built in step 2: 
    docker tag <IMAGE_ID> <YOUR_DOCKERHUB_USERNAME>/mern:latest​
  5. Register & login to Docker Hub
    docker login --username=<YOUR_DOCKERHUB_USERNAME>
  6. Push the Docker image to Docker Hub: 
    docker push <YOUR_DOCKERHUB_USERNAME>/mern
  7. Login to AppCloud
cf login -a https://api.lyra-836.appcloud.swisscom.com -u <YOUR_APP_CLOUD_USER>
  • Push the Docker image from Docker Hub to AppCloud:

 

cf push <APP_NAME> -o <YOUR_DOCKERHUB_USERNAME>/mern​

You might find it handy to follow the logs by running:

cf logs <APP_NAME>

Do you have experience with ReactJS, MERN, Docker or AppCloud? Your feedback is most welcomed!  

Glossary

Isomorphic App

Simply stated isomorphic applications are those one that can be run both on the client-side as well as on the server-side. While this definition is short and simple it does not reveal much of why one would care about isomorphism when building an app. Let’s try to dig this out: Looking at a present days’ web apps, we usually have a JavaScript-based front-end that communicates with Java-based backend. While this works nice and fine there are certain undesired effects such as:

  • code duplication – good example is pagination functionality. Such a functionality is usually implemented by a front-end and back-end pagination component, yet the goal is the same. Why can’t we just have one software component to build & maintain? Such a component would be an isomorphic one.
  • performance – this is linked to the “code duplication” problem. If you ever written any data fetching functionality you most probably have asked yourself why can’t you just fetch the data directly from the DB? Instead, we need to traverse several layers which rarely add anything but validation and connectivity to the database.

For more information take a look at: [1], [2], [3], [4]