avatarMarcos Henrique da Silva

Summarize

Another ExpressJS API tutorial for 2021, part 09 — Docker!

Hi guys, in this article we are going to configure our API made in ExpressJS and Typescript to run in a Docker container!

Hi there, if it is your first time arriving here we are creating a series on how to build an API using ExpressJS and Typescript. The first article can be found here.

Just as a quick overview, here is the series of articles that we are writing:

So today we are going to pause a bit our API coding to integrate our project into a Docker container. The main reason of doing this now are:

  • We want to use a real database for the next article;
  • It’s difficult to know which IOS you reader are using (Windows, Mac, Linux, …)
  • You already have a lot of the API working and it is fun to test it in a Docker container
  • With a docker container, we can make sure that what we do at our local machine should work exactly in a real environment

That all said, what is a Docker container?

A container is a standard unit of software that packages up code and all its dependencies so the application runs quickly and reliably from one computing environment to another. A Docker container image is a lightweight, standalone, executable package of software that includes everything needed to run an application: code, runtime, system tools, system libraries and settings. (Docker site)

Well, this is the starting basic idea but if you are more interested in learning Docker, you can start at this medium article

To start, please download the docker app for your OS and install using this link.

After installing, we will start to configure our files. Please make sure to use our latest GitHub branch that you can find it here.

We will give a standard receipt that works for a NodeJS application. To start, we want to have 3 files:

  • .dockerignore: that works similar to .gitignore
  • Dockerfile: here we will describe how to run our application image
  • docker-composer.yml: here we can configure how can we run one or more containers at the same time and make them to be able to communicate with each other

Dockerfiles describe how to assemble a private filesystem for a container, and can also contain some metadata describing how to run a container based on this image (Docker site)

Now, let’s create these three files at our root project folder. Remember that if there is no extension, you should not add .txt or something. The name of the files are exactly like: .dockerignore; Dockerfile; docker-composer.yml

Docker Composer: Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services (Docker site)

The dockerignore file: for now we just want to make sure that our container will not use the entire node_modules to save space at our container.

node_modules

The Dockerfile: here we are using the Dockerfile commands to use a ready image from the Docker hub. For now we are using node:10-slim but make sure to search about how to use Alpine images if you need to save a lot of memory in your live application.

FROM node:10-slim
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["node", "./build/app.js"]

For using the Dockerfile there is no much to tell, the algorithm is pretty simple: grab an image, create a folder, use this folder, copy everything of our root project (except node_modules), run npm install, make our port 3000 visible and run our builded app. If it is your first time using a Dockerfile, please read their docs here to understand it better.

The docker-composer.yml file: here we will start the configuration that will be used in the next article when we will start a MongoDB server without installing it at our OS ;)

version: '3'
services:
  api:
    command: npm run test-dev
    build: .
    volumes:
      - ./api:/app
    networks:
      - backend
    ports:
      - "3000:3000"
networks:
  backend:
    driver: bridge

For now just copying and pasting this “formula” would work to run our app at the port 3000. Again, if it is your first time using Docker and docker-composer, please make sure to read their documentation here. Just talking about all the magic behind of Docker, Dockerfile and docker-composer are enough of topic to several articles but let it make things simple here.

Let’s run and test it

Since we had made a configuration with docker-composer, here is how we would handle it:

At your root project, via terminal, run the following:

docker-compose build

You should expect something like:

Building api
Step 1/7 : FROM node:10-slim
---> 4bc78f8574a2
Step 2/7 : RUN mkdir -p /usr/src/app
---> Using cache
---> 807e720c3b34
Step 3/7 : WORKDIR /usr/src/app
---> Using cache
---> 58e8a3eb3d25
Step 4/7 : COPY . .
---> Using cache
---> 0fd49896f7a8
Step 5/7 : RUN npm install
---> Using cache
---> b2b6afefa85d
Step 6/7 : EXPOSE 3000
---> Using cache
---> 0de68cccb581
Step 7/7 : CMD ["node", "./build/app.js"]
---> Using cache
---> 45d29807c034
Successfully built 45d29807c034
Successfully tagged expressjs-api-tutorial_api:latest

Now, we have our build we can run the application. Run the command:

docker-compose up

And, we should expect something like:

Starting expressjs-api-tutorial_api_1 ... done
Attaching to expressjs-api-tutorial_api_1
api_1  |
api_1  | > expressjs-api-tutorial@0.0.1 test-dev /usr/src/app
api_1  | > nodemon --watch . --ext ts --exec "mocha -r ts-node/register test/**/*.test.ts"
api_1  |
api_1  | [nodemon] 2.0.2
api_1  | [nodemon] to restart at any time, enter `rs`
api_1  | [nodemon] watching dir(s): *.*
api_1  | [nodemon] watching extensions: ts
api_1  | [nodemon] starting `mocha -r ts-node/register test/**/*.test.ts`
api_1  | Server running at port 3000
api_1  | Routes configured for UsersRoute
api_1  | Routes configured for UsersRoute
api_1  |
api_1  |
api_1  | Created new instance of GenericInMemoryDao
api_1  |   ✓ should POST /users (118ms)
api_1  |   ✓ should POST to /auth and retrieve an access token (81ms)
api_1  |   ✓ should POST to /auth/refresh-token and receive 403 for having an invalid JWT
api_1  |   ✓ should POST to /auth/refresh-token and receive 401 for not having a JWT set
api_1  |   ✓ should POST to /auth/refresh-token and receive 400 for having an invalid refreshToken
api_1  |   ✓ should POST to /auth/refresh-token and retrieve a new access token
api_1  |   ✓ should DELETE /users/:userId
api_1  |   ✓ should POST /users (76ms)
api_1  |   ✓ should GET /users/:userId
api_1  |   ✓ should GET /users
api_1  |   ✓ should PUT /users/:userId
api_1  |   ✓ should GET /users/:userId to have a new name
api_1  |   ✓ should PATCH /users/:userId
api_1  |   ✓ should GET /users/:userId to have a new field called description
api_1  |   ✓ should DELETE /users/:userId
api_1  |   ✓ should GET /users
api_1  |
api_1  |   16 passing (318ms)

And that it! We have our application running inside a container and also running our test integration to keep coding safely!

Ok, ok… but how can I keep running tests meanwhile coding with a docker-compose file? That is quite simple, let’s add a script to our scripts at the package.json as the following:

"dev": "nodemon --watch . --ext ts --exec \"docker-compose build && docker-compose up\"",

Or, if you are that lazy, just copy and paste the updated package.json as the following:

{
  "name": "expressjs-api-tutorial",
  "version": "0.0.1",
  "description": "Tutorial of how to create a REST API using ExpressJS",
  "main": "dist/app.js",
  "scripts": {
    "tsc": "tsc",
    "start": "npm run tsc && node ./dist/app.js",
    "test-dev": "nodemon --watch . --ext ts --exec \"mocha -r ts-node/register test/**/*.test.ts\"",
    "dev": "nodemon --watch . --ext ts --exec \"docker-compose build && docker-compose up\"",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/makinhs/expressjs-api-tutorial.git"
  },
  "keywords": [
    "REST",
    "API",
    "ExpressJS",
    "NodeJS"
  ],
  "author": "Marcos Silva",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/makinhs/expressjs-api-tutorial/issues"
  },
  "homepage": "https://github.com/makinhs/expressjs-api-tutorial#readme",
  "dependencies": {
    "argon2-pass": "^1.0.2",
    "express": "^4.17.1",
    "jsonwebtoken": "^8.5.1",
    "short-uuid": "^3.1.1"
  },
  "devDependencies": {
    "@types/chai": "^4.2.8",
    "@types/express": "^4.17.2",
    "@types/mocha": "^7.0.1",
    "@types/mongoose": "^5.7.0",
    "@types/node": "^13.5.2",
    "@types/supertest": "^2.0.8",
    "chai": "^4.2.0",
    "mocha": "^7.0.1",
    "nodemon": "^2.0.2",
    "source-map-support": "^0.5.16",
    "supertest": "^4.0.2",
    "ts-node": "^8.6.2",
    "tslint": "^6.0.0",
    "typescript": "^3.7.5"
  }
}

Ok, now instead of running docker commands, at the root project with your terminal we should run the following:

npm run dev

You should expect to have a build then running application as a magic :) Now just change any part of your code, save and see it running again, like magic.

This article we configured a Docker container and Composer to run our API inside a container. At our next chapter we will configure a MongoDB and finally change the in memory DAO to something that make sense in the real life.

Thanks for reading and see you at the next article!

tips:

  • the full code of this article is here
  • don’t forget to use a nice app to test your API such as Postman or Insomnia
  • I know you still didn’t… Read the ExpressJS documentation!
  • Learn Docker, start at their Documentation!
  • Complete project is here! (Includes even the non existing articles yet)
Expressjs
Typescript
Docker
Tutorial
API
Recommended from ReadMedium