Dockerizing a Node.js / Express app from the very first [Part 2]
Tags: javascript, node, docker, tutorial
Published: 2022-06-20 08:32:31

- [Part 1](https://dev.to/msrumon/dockerizing-a-nodejs-express-app-from-the-very-first-part-1-50c1)

Previously, we created a Node-Express application completely off of Docker. We didn't need to have Node.js installed on our machine at all. We used Docker for basically scaffolding a new Node.js project and setting up the development workflow. In this article, we'll see how we can add a database solution to our current app without having any database server installed on our machine.

Let's start by editing our **docker-compose** file:

```yml
services:
  app:
    depends_on:
      - database
    image: node:lts-alpine
    working_dir: /usr/src/app
    volumes:
      - ./code:/usr/src/app:rw
    ports:
      - 8080:12345
    command:
      - npx
      - nodemon
      - -L
      - app.js
  database:
    image: postgres:alpine
    volumes:
      - postgres:/var/lib/postgresql/data:delegated
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: dockerized_node_express_app
    restart: always
  npm:
    image: node:lts-alpine
    working_dir: /tmp/app
    volumes:
      - ./code:/tmp/app:rw
    entrypoint:
      - npm
volumes:
  postgres: ~
```

We've just followed the [official documentation](https://github.com/docker-library/docs/blob/master/postgres/README.md) to add a PostgreSQL database server. This will act as an application container, as our app will soon be depended on it. And in order to make sure that the database container is always started whenever we start our application container, we've added a new `depends_on` key to the `app` service and let it know which other service(s) it, well, depends on. This will make sure that the database server is up and running before the app. You may also have noticed that we've mounted volume to our database server's datapath a bit differently. This is known as "named volume". We'll discuss it a bit more in another part, it's not relevant to what we're learning here.

Now, if we try to start the application using `docker compose up -d app` command, we'll see that the application will start up along with the database itself. Let's change our application code so that we can connect to the database from the application.

```javascript
const express = require('express');
const { Sequelize } = require('sequelize');

const {
  DB_HOST,
  DB_PORT,
  DB_USER,
  DB_PASS,
  DB_NAME
} = process.env;

const app = express();
const sequelize = new Sequelize({
  dialect: 'postgres',
  host: DB_HOST,
  port: DB_PORT,
  username: DB_USER,
  password: DB_PASS,
  database: DB_NAME,
  logging: false,
});

app.get('/', (req, res) => {
  res.json({ status: 200, message: 'Hello, world!' });
});

sequelize
  .authenticate()
  .then(() => console.log('Established connection with the database...'))
  .then(() => app.listen(12345))
  .then(() => console.log('Started the application server...'))
  .catch((error) => console.error(error.message));
```

> **NEVER USE DATABASE CREDENTIALS DIRECTLY IN THE CODE.**

Notice that we've got a few environment variables. We're running the application using Docker Compose, how are we supposed to tell Docker to set and define those? We use `environment` key.

```yml
services:
  app:
    depends_on:
      - database
    image: node:lts-alpine
    working_dir: /usr/src/app
    volumes:
      - ./code:/usr/src/app:rw
    environment:
      DB_HOST: database
      DB_PORT: 5432
      DB_USER: postgres
      DB_PASS: postgres
      DB_NAME: dockerized_node_express_app
    ports:
      - 8080:12345
    command:
      - npx
      - nodemon
      - -L
      - app.js
  database:
    image: postgres:alpine
    volumes:
      - postgres:/var/lib/postgresql/data:delegated
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: dockerized_node_express_app
    restart: always
  npm:
    image: node:lts-alpine
    working_dir: /tmp/app
    volumes:
      - ./code:/tmp/app:rw
    entrypoint:
      - npm
volumes:
  postgres: ~
```

Look at the `DB_HOST` variable. Usually we'd use `localhost`, but why are we using `database`? Well, we'd use `localhost` if we had both the application and the database server running on a single machine. Remember, we're using Docker, and all the containers here are completely isolated from each other, although each one of them is running on the same machine. It's like everything is being operated within its own "machine". And because of that, the application container doesn't know how to talk to the database server, it needs to know the IP address of that container. Luckily, if we use the name of the service of the container, Docker will internally resolve that to its IP address. That's how we establish communication among multiple containers.

We're almost done. Our app now needs few more dependencies, let's install them and finally bring up the app. We're gonna use our good-old `npm` utility container once more.

```bash
docker compose run --rm npm i sequelize pg
```

```bash
docker compose up app
```

> In case of you just started from this part and missed the previous one, run the following commands one after another: `docker compose run --rm npm init -y`, `docker compose run --rm npm i express`, `docker compose run --rm npm i -D nodemon`.

After a while (since the connection process is asynchronous and takes some time to finish), you'll see the success message appear in the terminal console. Yay! Such an awesome tool Docker is! *LONG LIVE THE DOCKER!*

Now that we've added database to our app and used a popular ORM "Sequelize", you should feel like home. Feel free to leverage that ORM (models, migrations, seeders etc.) and make this app more useful to the world.

In the next part, I'll discuss about file uploading. Because it appears that user-generated files are handled and managed a bit differently when you use Docker as a part of your development workflow. See ya there!