Building REST API with Express, TypeScript - Part 2: Docker Setup
Tags: node, express, typescript, docker
Published: 2021-07-31 18:12:03

---
title: Building REST API with Express, TypeScript - Part 2: Docker Setup
published: true
date: 2020-10-18 00:00:00 UTC
tags: Node, Express, TypeScript, Docker
canonical_url: https://rsbh.dev/blog/rest-api-express-typescript-docker
series: Building REST API with Express, TypeScript
---

In the [previous post](https://rsbh.dev/blog/rest-api-with-express-typescript), we build a REST API server with Express and TypeScript. In this post, we will dockerize the sever.

## Why Docker.

Docker helps organizations to ship and develop applications better and faster. It will be easy to set up the development environment on any new machine with docker as it abstracts out lots of complexity of setting up dependencies and environment. Docker also isolates the project from other projects in the same machine so the developer can run multiple projects without having any conflict with the required dependencies.

Docker makes it easy to configure and setup dependencies and environments for the application. As most of the companies have dedicated teams to do setup and manage infrastructure, Docker gives more power to developers to configure without depending on other teams to do the setup.

## Write Dockerfile.

To Dockerize the server, we need to create a `Dockerfile`. A Dockerfile is just a list of instructions to create a docker image. Read more about Dockerfile [here](https://docs.docker.com/engine/reference/builder/)

Each line in the Dockerfile is a command and create a new image layer of its own. Docker caches the images during the build, so every rebuild will only create the new layer which got changed from the last build. Here the order of commands is very significant as it helps to reduce the build time.

Let's start writing Dockerfile for the server. Here we are taking `node:12` as the base image for the server docker image. Explore [dockerhub](https://hub.docker.com/_/node) for more node image version. Here we are copying the `package.json` and doing `npm install` first, then copying the other files. Docker will cache the images of these two steps during the build and reuse them later as they change less frequently. Here we will be running the development server with the docker image, so we need to give `npm run dev` as the executing command.

`Dockerfile`

```dockerfile
FROM node:12

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 8000

CMD ["npm", "run", "dev"]
```

We need to add `.dockerignore` to tell docker build to ignore some files during the `COPY` Command.

`.dockerignore`

```ignore
node_modules
npm-debug.log
```

After creating the Dockerfile, we need to run the `docker build` to create a docker image from the Dockerfile. Here we are naming the docker image as `express-ts`

```bash
docker build -t express-ts .
```

We can verify the docker image by running the `docker images` command. Here we can see the name, size, and tag of the docker images.

```bash
docker images
REPOSITORY                        TAG                 IMAGE ID            CREATED             SIZE
express-ts                        latest              d0ce1e38958b        2 minutes ago       1.11GB
```

We can run the docker image with the `docker run` command. Here we can mapping the system port 8000 to docker container port 8000. We can verify if the server is running or not by visiting [http://localhost:8000/ping](http://localhost:8000/ping)

```bash
docker run -p 8000:8000 express-ts
```

## Add Docker Compose

The development server is running fine inside docker, but now we need to run the `docker build` command every time after making any changes to the source files to update the changes during development because the nodemon inside the docker container cannot watch the `src` folder on the local machine. We need to mount the local `src` folder to the docker container folder, so every time we make any change inside the `src` folder, nodemon restarts the development server inside the docker container.

We will add the `docker-compose.yml` file to the root of the project to mount the local `src` folder. Read more about docker-compose [here](https://docs.docker.com/compose/)

`docker-compose.yml`

```yaml
version: "3"

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ./src:/app/src
    ports:
      - "8000:8000"
```

We need to run the command `docker-compose up` to start the server. Now the server is running in development mode with auto-restart on code changes. We can verify the server is restarting on code changes by making any code change in the TypeScript files.

```bash
docker-compose up
```

The docker setup for the development server is completed. Let's rename the Dockerfile as `Dockerfile.dev` and update the docker-compose.yaml file. We will use the `Dockerfile` for the production image, which we are going to set up in the next section.

```bash
mv Dockerfile Dockerfile.dev
```

`docker-compose.yml`

```yaml
version: "3"

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - ./src:/app/src
    ports:
      - "8000:8000"
```

## Add Production Dockerfile

Let's start building a docker image for the production server. We need to create a new Dockerfile and add the following commands. Here after copying the files, we need to build the JavaSript files and execute the `npm start` command.

```dockerfile
FROM node:12

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

RUN npm run build

EXPOSE 8000

CMD ["node", "start"]
```

After running the `docker build` command, we can see the docker image is created for the production server.

```bash
docker images
REPOSITORY                      TAG                 IMAGE ID            CREATED             SIZE
express-ts                      latest              d0ce1e38958b        2 minutes ago       1.11GB
```

Here the image size is `1.11GB`, which is not optimized. Let's optimize the docker image and reduce the size.

First, instead of taking `node:12` as the base image, we will be taking its alpine variant. Alpine Linux is very lightweight. Read more about alpine-docker [here](http://gliderlabs.viewdocs.io/docker-alpine/).

```dockerfile
FROM node:12-alpine
```

Let's build the docker image with the updated Dockerfile. Here we are tagging the docker image as `alpine` so we can compare the image size with the previous build.

```bash
docker build -t express-ts/alpine .
```

```bash
docker images
REPOSITORY                       TAG                 IMAGE ID            CREATED             SIZE
express-ts                       alpine              2b06fcba880e        46 seconds ago      280MB
express-ts                       latest              d0ce1e38958b        2 minutes ago       1.11GB
```

After running the `docker images` command we can see the difference in the sizes of docker images. The docker image is much leaner than the previous build.

There are still some issues with our docker image as development dependencies are there in production build and TypeScript code is there, which is not required while running the server in production. So let's optimize the docker image further with a multi-stage build.

Here we create two stages, one for building the server and the other for running the server. In the builder stage, we generate Javascript code from the Typescript files. Then in the server stage, we copy the generated files from the builder stage to the server stage. In the Server stage, we need only production dependencies, that's why we will pass the `--production` flag to the `npm install` command.

```dockerfile
FROM node:12-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM node:12-alpine AS server
WORKDIR /app
COPY package* ./
RUN npm install --production
COPY --from=builder ./app/public ./public
COPY --from=builder ./app/build ./build
EXPOSE 8000
CMD ["npm", "start"]
```

Let's build the docker image with the updated multi-staged Dockerfile. Here we are tagging the docker image as `ms` so we can compare the image sizes with the previous builds.

```bash
docker build -t express-ts/ms .
```

```bash
docker images

REPOSITORY                       TAG                 IMAGE ID            CREATED             SIZE
express-ts                       alpine              2b06fcba880e        46 seconds ago      280MB
express-ts                       latest              d0ce1e38958b        2 minutes ago       1.11GB
express-ts                       ms                  26b67bfe45b0        9 minutes ago       194MB
```

After running the `docker images` command we can see the difference in the sizes of docker images. The multi staged image is the leanest among all images.

We have dockerized the development and production version of the Express and TypeScript REST API server.

All the source code for this tutorial is available on [GitHub](https://github.com/rsbh/express-typescript).

### Next

- [Building REST API with Express, TypeScript - Part 3: PostgreSQL and Typeorm](https://rsbh.dev/blog/rest-api-express-postgres-typeorm)
- [Building REST API with Express, TypeScript - Part 4: Jest and unit testing](https://rsbh.dev/blog/rest-api-express-typescript-jest-testing)