Setup Docker Nodemon and Express

One of the benefits of developing locally with Docker is that it gives you the closest representation of your production environment and minimizes those "well it works fine locally" deployment moments. The annoying part is that you can't access the files in your docker container directly from your host machine and so each time you want to change something you need to rebuild the image. This breaks something like nodemon, unless you use bind mounts.

This project shows you how to set up a simple Express NodeJS server that gets some data from the Pokemon API and relays it back to you. It runs in a docker container and automatically restarts using Nodemon when it detects file changes.

Project can be found at github.com/simonbarker/docker-nodemon

Note: this is a simple example project, there are many ways to improve this (dockerignore file and switching to user node etc).

Setup the Express project

Start a new project and run the following in a new project directory (like myTestDockerProject):

npm init
npm i express --save
npm i node-fetch --save
npm install nodemon --save-dev

We need Express to set up a server and node-fetch to make simple client requests to the Pokemon API. Setup the following directory structure in your project directory:

index.js
services/pokemon-service.js

and put the following in the index.js:

const PokemonService = require('./services/pokemon-service');
const express = require('express');

const app = express();
const port = 3000;

let pService = new PokemonService();

(async() => {

  app.get('/getPokemon', async (req, res) => {
    let pokemon = await pService.getData('https://pokeapi.co/api/v2/pokemon/');
    res.send(pokemon);
  })

  app.listen(port, () => {
    console.log(`listening on port ${port}`);
  })

})()

and this in services/pokemon-service.js:

const fetch = require('node-fetch');

class PokemonService {
  constructor() { }

  getData = async url => {
    try {
      const response = await fetch(url);
      return await response.json();
    } catch (error) {
      console.log(error);
    }
  };
}

module.exports = PokemonService;

Run an npm start and then hit this url in your browser localhost:3000/getPokemon and you should get a json response of a list of Pokemon in your browser. If you change a file in your project now you would have to restart the server, we can change this with nodemon. Add the following line to your package.json in the scripts section:

"start-dev": "nodemon index.js"

and then run npm run start-dev in your terminal. Same result as before but now if you change a file (like add a console.log(); in index.js file you will see nodemon will stop and restart the server.

Let's put this in a Docker container

We need to add two files at the top level of the project: Dockerfile and docker-compose.yml, the Dockerfile defines the image you need to build and the docker-compose is a nice quality of life tool to make your development builds easier. Put this in your Dockerfile:

# the image of node we want to base this image from
FROM node:12.18.4-alpine

# create and move in to a www directory
WORKDIR /var/www/

# copy the contents of the project into the new directory
COPY . .

# npm install 
RUN npm install

# run without nodemon in docker file, docker-compose will override to run with nodemon
CMD [ "npm", "run", "start" ]

and put this in your docker-compose.yml file

version: "3"
services: 
  node:
    build: .
    image: node-local
    ports:
      - 3000:3000
    volumes:
      - .:/var/www/
    command:
      [ "npm", "run", "start-dev" ]

This:

  • sets up a service called node
  • builds using the local Dockerfile
  • maps the containers port 3000 to localhost port 3000
  • - .:/var/www/ maps the current directory to the containers /var/www directory meaning. The means that any files you edit, add or delete in your project will now cause nodemon to restart you project without rebuilding the container image.

Spin up

In the root of you project directory now run docker-compose up, this will build the docker image and start the container using nodemon.

Now navigate to localhost:3000/getPokemon in your browser and you should see the same list of Pokemon, except this time if you edit a source file (add a console.log() somewhere) you will see in your terminal that nodemon restarts the app for you.

Note: if you want to use this without nodemon (as you should in production) then the Dockerfile still uses the standard npm start script.