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
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.
- Download the source code from the author’s GitHub.
- Extract the contents into your working folder, which will be mapped to the docker image we previously created.
- Configure the file
config.js
to match the MongoDB settings above. The authentication uses a symmetric keysecret
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.
- 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 installednodemon
. If you are using the official image, just usenode 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:
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
[{"_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.
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.
-
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.
-
Extract into a folder called
backend
. -
Open up a Terminal and
cd
to the backend folder. -
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
-
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"
-
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 ****************************
-
Download the frontend server source code vue-skeleton-mvp from GitHub
-
Extract into a folder called
frontend
. -
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
.
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
.