Express Application with MongoDB

Build an Express Application that uses MongoDB and Docker

Built with tons of help from:

Contents

Setting up Mongo

# Adding Mongo to your PATH

If you have just downloaded and installed MongoDB, it may not be defined as a system variable in your PATH, you can do that finding the Mongo installation directory and adding this as a system environment variable, the directory should be like the following for Windows

C:\Program Files\MongoDB\Server\4.0\bin\

This will give us access to both mongo and monod commands

# Create a Data Directory

Next we will need a place to store our data, we can create this directory anywhere we want, in this case I'll make it inside of my app directory

mkdir mongodata
cd mongodata

mkdir data

# Running the DB Server

Next we can run our database server with the following command inside of our mongo directory that we just created

mongod --dbpath .\data\

If we see an output with

...
2018-12-21T09:01:21.883+0200 I NETWORK  [initandlisten] waiting for connections on port 27017
2018-12-21T09:01:21.928+0200 I INDEX    [LogicalSessionCacheRefresh] build index on: config.system.sessions properties: { v: 2, key: { lastUse: 1 }, name: "lsidTTLIndex", ns: "config.system.sessions", expireAfterSeconds: 1800 }
2018-12-21T09:01:21.928+0200 I INDEX    [LogicalSessionCacheRefresh]     building index using bulk method; build may temporarily use up to 500 megabytes of RAM
2018-12-21T09:01:21.936+0200 I INDEX    [LogicalSessionCacheRefresh] build index done.  scanned 0 total records. 0 secs

We know that the server is running

# Creating and Viewing Elements

In a new terminal window we open the Mongo Shell with

mongo

# Connect to a Database

Next we need to connect to our database to access data, we can do this from the Mongo Shell with

use mongodata

If successful we will see the output

switched to db mongodata

# Insert Data

Next we can try to insert an element with the following

db.comments.insert({"name":"myuser", "comment":"Hell World!"})

Mongo uses BSON (basically JSON with some sprinkles) for data storage

We can also insert data as follows

newstuff = [{"name":"Nabeel Valley","comment":"Hello Nabeel. Weather good today"},{"name":"Kefentse Mathibe","comment":"I'm OK!"}]
db.comments.insert(newstuff)

# View Data

We can view our inserted data with

db.comments.find().pretty()

Which will output the following

{
        "_id" : ObjectId("5c1c93222712ad7e454a01a2"),
        "username" : "myuser",
        "email" : "me@email.com"
}
{ "_id" : ObjectId("5c1c94562712ad7e454a01a3"), "name" : "nabeel" }
{
        "_id" : ObjectId("5c1c94562712ad7e454a01a4"),
        "email" : "unknown@email.com",
        "age" : 7
}

Building the Express App

This can all be found in the server.js file

The Express app will do a few things:

  • Serve the necessary static files for the app
  • Get and Insert data into Mongo
  • Build and send the Mongo content to the frontend

# Importing the Necessary Libraries

I'm making use of the following libraries to

  • Read environmental variables
  • Create my server
  • Parse JSON body from the create form
require('dotenv').config()

// configure express
const express = require('express')
const app = express()
const port = process.env.PORT || 8080
const bodyParser = require('body-parser')

# Configure the Database

I'm using monk to easily interface with our database

const mongo = require('mongodb')
const mongo = require('mongodb')
const monk = require('monk')
const mongoEndpoint = process.env.MONGO_ENDPOINT || 'mongo:27017/mongodata'
const db = monk(mongoEndpoint)

I've used the default value of mongo:27017 for when the application is run in k8s in order to interface with a mongo instance more easily. If running locally we can set the MONGO_ENDPOINT in a .env file in the project root directory as follows

MONGO_ENDPOINT=localhost:27017

# Middleware

Configure express middleware for the following

  • Use static files
  • Parse JSON and Form data from request
  • Make DB accessible to the app
app.use(bodyParser.json())
app.use(bodyParser.urlencoded())
app.use(express.static('public'))

app.use((req, res, next) => {
    req.db = db
    next()
})

# View Comments

Next I define a /comments that will retrieve content from the comments collection, render it with the base.card and base.content functions and send that as a response

app.get('/comments', function(req, res) {
    let db = req.db
    let collection = db.get('comments')
    collection.find({}, {}, function(e, docs) {
        const base = require('./base')

        let content = ''
        docs.reverse().forEach(comment => {
            content += base.card(comment.name, comment.comment, comment._id)
        })
        content = base.content(content)

        res.send(content)
    })
})

# Creeate Comment

To create a comment I've used a simple form in the frontend, which can be seen below

<form action="/submit" method="post">
    <div class="form-row">
        <div class="col-lg-12 mb-3">
            <label for="nameInput">Full Name</label>
            <input
                type="text"
                class="form-control"
                id="nameInput"
                placeholder="John Doe"
                value=""
                required
                name="name"
            />
        </div>
        <div class="col-lg-12 mb-3">
            <label for="commentInput">Comment</label>
            <input
                type="text"
                class="form-control"
                id="commentInput"
                placeholder=""
                value=""
                required
                name="comment"
            />
        </div>
    </div>
    <button class="btn btn-primary" type="submit">Submit form</button>
</form>

And an express route to handle the post and insert the new comment to the database

app.post('/submit', (req, res) => {
    // Set our internal DB variable
    let db = req.db

    // Set our collection
    let collection = db.get('comments')

    // Submit to the DB
    collection.insert(req.body, function(err, doc) {
        if (err) {
            // If it failed, return error
            const base = require('./base')
            const content = base.content(
                '<h1>There was an error sending your content, please try again<h2>'
            )
            res.send(content)
        } else {
            // get id of inserted element
            console.log(doc._id)
            // And forward to success page
            res.redirect(`comments`)
            // res.redirect(`comments/${doc._id}`)
        }
    })
})

Deploy on k8s

Once we are done we can push this as a Docker image and deploy it on a Kubernetes Cluster as follows

# Building the Image

From the application directory run

docker build -t <USERNAME>/comments-app
docker push

# Deploying on Kubernetes

Once logged into a kubernetes cluster we can make use of the express.yaml to deploy the express app, and the mongo.yaml file to deploy Mongo

kubectl create -f express.yaml
kubectl create -f mongo.yaml

This will create a deployment as well as a service for both the Express App and Mongo. The deployment configs are as follows

express.yaml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  labels:
    app: comments-app
  name: comments-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: comments-app
  template:
    metadata:
      labels:
        app: comments-app
      name: comments-app
    spec:
      containers:
      - image: nabeelvalley/comments-app
        imagePullPolicy: Always
        name: comments-app

---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: comments-app
  name: comments-app
spec:
  ports:
  - name: tcp-8080-8080-comments-app
    nodePort: 30016
    port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: comments-app
  type: LoadBalancer

mongo.yaml

apiVersion: v1
kind: Service
metadata:
   name: mongo
   labels:
     run: mongo
spec:
   ports:
   - port: 27017
     targetPort: 27017
     protocol: TCP
   selector:
     run: mongo

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
   name: mongo
spec:
   template:
     metadata:
       labels:
         run: mongo
     spec:
       containers:
       - name: mongo
         image: mongo
         ports:
         - containerPort: 27017

Running Locally

If you'd like to run this application on a local Kubernetes cluster, take a look at the page on Deploying an Express App that Uses Mongo on k8s Locally