Previously, I described my development environment setup using Docker containers. I do it in a totally different way from most tutorials, which I don’t believe leverage containers and complicate things by having to build images for development.

Here I explain how I wire up (i.e. network) a few containers - a MongoDB database, a Mongo Express User Interface to manage the database, and a Node.js container using JSON Web Tokens (JWT) to authenticate users against the database.

This is the second in a series of posts on my journey with containers and orchestration. You should by Setting up Node.js in a Docker container if you haven’t.

Wiring up MongoDB and Mongo Express

In my working folder, I created a directory called data where the database files will reside. The MongoDB container will store database files in this directory.

Head over to Docker Hub and read up on Mongo and Mongo-Express image configuration. For Mongo, we should specify a username and password, and we can:

  • Provide the admin username and password in the command line -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=password,
  • Or define the password in a file, i.e. -e MONGO_INITDB_ROOT_PASSWORD_FILE=data/password_filename,
  • But me, I’d rather just put everything in an environment variable file - this is what we do next.

In fact, I’m so lazy I combined both the MongoDB and Mongo Express configuration parameters env.list in the data folder:

MONGO_INITDB_ROOT_USERNAME=admin
MONGO_INITDB_ROOT_PASSWORD=this-is-my-password
ME_CONFIG_MONGODB_SERVER=mongodev
ME_CONFIG_MONGODB_ADMINUSERNAME=admin
ME_CONFIG_MONGODB_ADMINPASSWORD=this-is-my-password

To be clear, this is simply a setup for developers. You don’t manage passwords this way this in production! In addition, I only advocate downloading official images off Docker Hub - even if someone created an image which combines MongoDB + Mongo Express, don’t use what you can trust.

One more thing to explain, for containers to communicate we need to setup a bridge network to link them together. MongoDB already listens on port 27017, but I also need to allow my host to get to the Mongo Express UI.

docker pull mongo #optional, docker run also does this if it can’t find "mongo"
docker pull mongo-express
docker network create netdev #create a network for containers to communicate
docker run --name mongodev -d -v $PWD/data:/data/db --network netdev --env-file data/env.list mongo
docker run --name mongoexpressdev --rm --network netdev -p 8081:8081 --env-file data/env.list mongo-express

The last 3 lines will give you an output similar to this. If you get everything "connected", then open your browser to localhost:8081, and you’ll get the Mongo Express UI.

~/code$ docker network create netdev
14be4390a01e0ebd9608fc62b76518c0f0d9f918810a234f6c746ef95eefff54
~/code$ docker run --name mongodev -d -v data:/data/db --network netdev --env-file data/env.list mongo
1d7a2510c6a5a5682581aa4510389bc5a97159a0b4b2744d16320e3ce47d8c28
~/code$ docker run --network netdev -p 8081:8081 --env-file data/env.list mongo-express
Waiting for mongodev:27017...
Welcome to mongo-express
------------------------

Mongo Express server listening at http://0.0.0.0:8081
Server is open to allow connections from anyone (0.0.0.0)
basicAuth credentials are "admin:pass", it is recommended you change this in your config.js!
Database connected
Admin Database connected

Mongo Express database management user interface

When you’re done, Ctrl-C will stop Mongo Express.

To stop MongoDB, docker stop mongodev, to re-start docker start mongodev. However, I generaly like to start with fresh containers, hence I add --rm when I start MongoDB, so that when I stop it, the container is deleted. Most people don't do it this way, because it's faster / easier re-start named containers.

In addition, of course you can run Mongo Express in the background too with -d, but I want to see the console... it’s up to you!

If you need to check your container names, docker ps -a lists all containers created and/or running. If you get an error to the effect that The container name "/mongodev" is already in use by container, then delete the container with docker rm mongodev.

Wiring up a Node.JS container with JSON Web Token (JWT) sample code

Now, I’m just going to basically follow the tutorial Authenticate a Node.js API with JSON Web Tokens.

I don’t endorse the code, and in fact it’s simple but certainly far from complete - I simply wanted to demonstrate how I would leverage networked containers to develop and debug code.

  1. Download the source code from the author’s GitHub.
  2. Extract the contents into your working folder, which will be mapped to the docker image we previously created.
  3. Configure the file config.js to match the MongoDB settings above. The authentication uses a symmetric key secret which you can also change:
    module.exports = {
    'secret': 'this-is-my-key',
    'database': 'mongodb://admin:this-is-my-password@mongodev:27017/admin'
    };

I’m just reusing the “admin” user and database - you can of course use the Mongo Express UI to create your own database and user. Just change the database string above.

  1. Now, open Terminal and in your working folder run our previous custom node container, this time with the --network defined.
    docker run -it --rm --network netdev -v $PWD:/code -p 8080:8080 -p 127.0.0.1:9229:9229 nodemondev bash -c "cd code; npm install; nodemon --inspect=0.0.0.0:9229 server.js"

    You only need npm install the first time (or anytime you want to update the dependencies), but I leave it it since it doesn’t take long. nodemondev is a custom image created in my previous post, where I installed nodemon. If you are using the official image, just use node server.js

You could run everything manually, i.e. docker run -it --rm --network netdev -v $PWD:/code -p 8080:8080 -p 127.0.0.1:9229:9229 nodemondev bash and then in the Node.js container console, run npm install followed by nodemon server.js. However, I’d like to debug the code in Visual Studio Code and use the "Attach by Process ID" capability, which requires that the container is run with --inspect in the command line.

To test everything is wired up, in your host curl -G localhost:8080/setup and you’ll see returned:

{"success":true}

In the Mongo Express you can see the sample “registered” user Nick Cerminara inserted:

Mongo Express JWT sample code database

From here on, the original tutorial I’m referring to uses Postman to test the authentication process. I’m using good old curl instead, since I know what I’m doing and it’s easier to post commands here rather than screenshots!

Now the database is populated with a default user. Next, to test the user authenticating, run curl -H "Content-Type: application/x-www-form-urlencoded" -d "name=Nick+Cerminara&password=password" localhost:8080/api/authenticate, which returns:

{"success":true,"message":"Enjoy your token!","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhZG1pbiI6dHJ1ZSwiaWF0IjoxNTU1MDgxMzU0LCJleHAiOjE1NTUxNjc3NTR9.kPqlKz1cFB-QZNaP7w7C-3xT_YzAtijKXjnVB8HqgEk"}

If the authentication fails, you’ll see the following instead:

{"success":false,"message":"Authentication failed. Wrong password."}

Finally, to simulate an authenticated user calling an API to query the database, curl -G -H "x-access-token:<token>" localhost:8080/api/users (replacing with the value from the previous curl return). On success:

[{"_id":"5cb0a2e0c06bb9004422b6fe","name":"Nick Cerminara","password":"password","admin":true,"__v":0}]

If the token is wrong or missing, you’ll see:

{"success":false,"message":"Failed to authenticate token."}

And since we are debugging, I can place a breakpoint from VS Code and learn what the code is doing line by line, e.g.

Visual Studio Code Debugging JWT Sample code

Trying out more JWT sample code

I found a decent pre-built skeleton comprising a Node.js backend and a Node.js + Vue.js front end by Daniel Avellaneda. Again, no endorsement, but this is how I tested it myself.

  1. Download the backend server soure code node-express-mongodb-jwt-rest-api-sekeleton from GitHub by hitting the Clone or download button in the top right and selecting Download ZIP.

  2. Extract into a folder called backend.

  3. Open up a Terminal and cd to the backend folder.

  4. Copy the sample environment file to a new file cp .env.example .env, and edit it your MongoDB credentials:

    PORT=3000
    JWT_SECRET=MyUltraSecurePassWordIWontForgetToChange
    JWT_EXPIRATION_IN_MINUTES=4320
    MONGO_URI=mongodb://admin:this-is-my-password@mongodev:27017/admin
    EMAIL_FROM_NAME=My Project
    EMAIL_FROM_ADDRESS=info@myproject.com
    EMAIL_SMTP_DOMAIN_MAILGUN=myproject.com
    EMAIL_SMTP_API_MAILGUN=123456
    FRONTEND_URL=http://localhost:8080
    USE_REDIS=false
    REDIS_HOST=127.0.0.1
    REDIS_PORT=6379                      
  5. Now, seed the database with a sample data. You'll see a message Seed complete!

    docker run -it --rm --network netdev -v $PWD:/code -p 3000:3000 -p 127.0.0.1:9259:9229 nodemondev bash -c "cd code; npm install; npm run seed"
  6. Make sure MongoDB is running first, then run the backend server:

    docker run -it --rm --network netdev -v $PWD:/code -p 3000:3000 -p 127.0.0.1:9269:9229 nodemondev bash -c "cd code; npm install; npm run dev"

    You'll see:

    ****************************
    *    Starting Server
    *    Port: 3000
    *    NODE_ENV: development
    *    Database: MongoDB
    *    DB Connection: OK
    ****************************
  7. Download the frontend server source code vue-skeleton-mvp from GitHub

  8. Extract into a folder called frontend.

  9. Finally, from the frontend folder, run:

    docker run -it --rm --network netdev -v $PWD:/code -p 8080:8080 -p 127.0.0.1:9269:9229 nodemondev bash -c "cd code; npm install; npm run serve"

    You'll get:

    App running at:
    - Local:   http://localhost:8080/ 
    
    It seems you are running Vue CLI inside a container.
    Access the dev server via http://localhost:<your container's external mapped port>/
    
    Note that the development build is not optimized.
    To create a production build, run npm run build.

Make sure both instances of the nodemondev container have different debugging ports exposed to the host, i.e. 9259 and 2969. Otherwise, the Docker container will not run, saying Bind for 127.0.0.1:9229 failed: port is already allocated.

  1. All good, now hit up the frontend UI in your browser at localhost:8080: Sample code login page Skeleton code data page

Conclusion

When using multiple containers, make sure to given containers a --name and wire them up with a --network. Finally, make sure you use different port number mappings -p.