Setting Up Node App using Docker
Tags: docker, tutorial, node
Published: 2023-01-16 08:00:00

## Create a docker setup to build an Express app with MySQL and Redis

1. Create a directory and go switch to it
    ```sh
    mkdir nodeapp
    cd nodeapp
    ```

2. Create `package.json`

    To avoid installing Node locally, we will use this docker command to `npm init`
    ```sh
    docker run --rm -v "$PWD:/$(basename $PWD)" -w "/$(basename $PWD)" -it node:current-alpine sh -c "npm init"
    ```
3. Install `dotenv` and `express` packages
    ```sh
    docker run --rm -v "$PWD:/$(basename $PWD)" -w "/$(basename $PWD)" -it node:current-alpine sh -c "npm install dotenv express"
    
    docker run --rm -v "$PWD:/$(basename $PWD)" -w "/$(basename $PWD)" -it node:current-alpine sh -c "npm install --save-dev nodemon"
    ```
4. Edit `package.json` to add to `scripts` section
    ```json
    "start": "node index.js",
    "dev": "nodemon index.js"
    ```

5. Create `.env`

    ```ini
    APP_PORT=3000
    DOCKER_PORT=3000
    
    #db is the service name in docker-compose.yml
    DB_HOST=db
    DB_PORT=3306
    DB_NAME=nodeapp
    
    #can be left blank if using root username
    DB_USERNAME=
    DB_PASSWORD=
    DB_PORT=3306
    
    #redis is the service name in docker-compose.yml
    REDIS_HOST=redis
    REDIS_PASSWORD=null
    REDIS_PORT=6379
    ```

6. Create `Dockerfile`
    ```docker
    #change node version to your liking
    FROM node:18
    WORKDIR /app/
    COPY ./package.json .
    RUN npm install
    COPY ./* .
    EXPOSE $DOCKER_PORT
    ```

7. Create `docker-compose.yml`

    ```yaml
    version: '3'
    services: 
      web:
        build:
          context: .
        env_file: ./.env
        tty: true
        volumes: 
          - ./:/app/
          - node_modules:/app/node_modules
        ports:
          - '${APP_PORT:-3000}:${DOCKER_PORT:-3000}'
        depends_on: 
          - db
        networks:
          - nodeapp
      db:
        image: mysql:latest
        env_file: ./.env
        environment: 
          MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
          MYSQL_ROOT_HOST: "%"
          MYSQL_DATABASE: '${DB_NAME}'
          MYSQL_USER: '${DB_USERNAME}'
          MYSQL_PASSWORD: '${DB_USERNAME}'
          MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
        ports:
          - '${DB_PORT:-3306}:3306'
        volumes:
          - mysql:/var/lib/mysql
        networks:
          - nodeapp
      redis:
        image: 'redis:alpine'
        ports:
          - '${REDIS_PORT:-6379}:6379'
        volumes:
          - 'redis:/data'
        networks:
          - nodeapp
        healthcheck:
          test: ["CMD", "redis-cli", "ping"]
          retries: 3
          timeout: 5s
    networks:
      nodeapp:
        driver: bridge
    volumes:
      node_modules:
        driver: local
      mysql: 
        driver: local
      redis:
        driver: local
    ```

8. Create `index.js`
    ```js
    require('dotenv').config();

    const express = require('express');
    const app = express();

    // Constants
    const PORT = process.env.DOCKER_PORT || 3000;
    const HOST = '0.0.0.0';

    // App
    app.get('/', (req, res) => {
        res.send('Hello World!');
    });

    app.listen(PORT, HOST, () => {
         console.log(`Running on http://${HOST}:${PORT}`);
    });
    ```
9. Run `docker-compose up -d`
10. Run `docker-compose exec web npm run dev`
11. Go to `http://localhost:3000` ***3000 will be APP_PORT***

## BONUS
To easily run `npm` commands, do the following

1. Create a file with the name `soar`
    
    ```sh
    #!/bin/bash
    
    path=$(printf '%s\n' "${PWD##*/}")
    
    # get first arg
    arg1=$1
    
    if [[ $arg1 == "up" ]]; then
        command="docker-compose up -d"
        $command
    elif [[ $arg1 == "down" ]]; then
        command="docker-compose down"
        $command
    else
        # remove first arg
        shift
    
        # get rest of args
        argrest=$@
    
        # web is the service name in docker-compose.yml
        ccommand="docker-compose exec web $arg1 "$argrest""
    
        # run the command
        $command
    fi
    ```
    
2. Make `soar` runnable
    
    ```sh
    chmod 700 soar
    ```
    
3. Test it with
    
    ```sh
    ./soar npm -h
    ```
    
4. Create an alias for `./soar` to `soar`
    
    ```sh
    alias './soar'
    ```
    
    To make sure this is always available, you may add this to your shell configuration file in your home directory, such as `~/.zshrc` or `~/.bashrc`, and then restart your shell.
    
5. Now you can start docker, run npm, or npx
    
    Start docker container
    
    ```sh
    soar up
    ```
    
    Stop docker container
    
    ```sh
    soar down
    ```
    
    Run npm
    
    ```plaintext
    soar npm list
    ```
    
    Run npx
    
    ```plaintext
    soar npx ...
    ```