In this article we are going to explore how to Dockerize a Node.js/Typescript which uses MongoDB as its database. Docker, a platform for creating, deploying, and running applications in containers, is becoming a staple in modern web development. We are going to run Node.js/Typescript application and MongoDB in separate containers and we will combine this multi container application using docker compose.
Prerequisites
- A basic understanding of Node.js, TypeScript, and MongoDB.
- Docker and Docker Compose installed on your machine.
If you need help for installing Docker and Docker Compose please checkout the links below
Docker Installation
Windows Installation – https://docs.docker.com/desktop/install/windows-install/
Ubuntu Installation – https://docs.docker.com/engine/install/ubuntu/
Mac Installation – https://docs.docker.com/desktop/install/mac-install/
Docker Compose
Installation : https://docs.docker.com/compose/install/
Step -1 Setting up Node.js/Typescript application
Create and enter directory for the application
mkdir nodejs-ts-mongo-app && cd nodejs-ts-mongo-app
Step-2 Create Package.json
npm init -y
Step-3 Install the required packages
npm install express mongoose config
npm install -D typescript ts-node @types/node @types/express @types/config
Step-4 Create tsconfig.json file
tsc --init
or
npx tsc --init
Either of this above command will create the tsconfig.json on your project directory
Modify your tsconfig.json file and remove the unnecessary dependencies . The final tsconfig file should look like the following
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": ["es6"],
"allowJs": true,
"outDir": "build",
"rootDir": "src",
"strict": true,
"noImplicitAny": true,
"esModuleInterop": true,
"resolveJsonModule": true
}
}
The rootDir has been setup as “src” because we are going to create all our application codes inside the “src” folder of our application
Step-5 Create Source code directory and app.ts
Create a directory “src” under your project directory and create a file app.ts under the directory. After creating the app.ts file put the following code inside the app.ts file
import express, {Application} from 'express'
const app:Application = express();
const PORT = 4000;
app.listen(PORT,()=>{
console.log(`Server started at port ${PORT}`)
})
Step-6 Modify package.json
In our package.json file under script we will add some code like the following
"dev": "node build/app.js",
"build":"tsc"
Your package.json should look like this
{
"name": "nodejs-ts-mongo-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type":"commonjs",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "node build/app.js",
"build":"tsc"
},
"keywords": [],
"author": "Anupam <Anupam Roy> (https://codingcharcha.com/)",
"license": "ISC",
"dependencies": {
"config": "^3.3.9",
"express": "^4.18.2",
"mongoose": "^7.4.3"
},
"devDependencies": {
"@types/config": "^3.3.0",
"@types/express": "^4.17.17",
"@types/node": "^20.5.0",
"ts-node": "^10.9.1",
"typescript": "^5.1.6"
}
}
You may not see some specific entries like “author” . You can ignore that
Step-7 Creating Dockerfile for Node.js
Dockerfiles are text documents that allow you to build images for Docker. Create a file ‘Dockerfile’ and place it under the root of the current project and write the following lines of code
# Use official Node.js image as base
FROM node:latest
# Set working directory
WORKDIR /usr/src/app
# Copy package.json and package-lock.json
COPY package*.json ./
# Install app dependencies
RUN npm install
# Copy all source files
COPY . .
# Transpile Typescript to Javascript
RUN npm run build
# Command to run the app
CMD ["npm", "run","dev"]
Step-8 Creating the docker-compose file
Though at this point of time we don’t need docker-compose but we will create it so that we can run the app through docker-compose alternatively we can also run the app using just docker commands like “docker build”. Lets create another file docker-compose.yaml and place it under the root of the project
version: '3.7'
services:
app:
container_name: app
build: ./
ports:
- 4000:4000
Lets see what each line in the file
‘version’ : Which version of docker-compose is being used
‘services’ : A service is an abstract definition of a computing resource within an application which can be scaled or replaced independently from other components. Services are backed by a set of containers, run by the platform according to replication requirements and placement constraints. A Compose file must declare a services
top-level element as a map whose keys are string representations of service names, and whose values are service definitions. A service definition contains the configuration that is applied to each service container.
‘app’ : It is the service for Node.js/Typescript application which contains
‘container_name’ : The name of the docker container that will run the Nodejs/Typescript application
‘build’: It specifies the build configuration for creating a container image from source code. As we have placed our docker-compose file in the root of the Project directory so the path to build will be ‘./’
‘ports’: ports always be in the form of HOST_PORT:CONTAINER_PORT . The HOST_PORT refers to the port where the service can run internally inside the container. The CONTAINER_PORT on the other hand refers to the port through which other services can access the service running inside the container. In this case both are 4000. But in many cases these two can be different
Step-9 Testing the Node.js/Typescript app through the docker-compose command
In order to run and test our app use the following command
docker-compose up --build
After you test it successfully. Lets move ahead and add MongoDB in the equation.
Step-10 Adding MongoDB to docker-compose as a service
We are going to add MongoDB as a service to the docker-compose file. This time we won’t need a Dockerfile rather we will use MongoDb official image to build the service. After adding the MongoDB in docker-compose the file should look like the following
version: '3.7'
services:
app:
container_name: app
build: ./
ports:
- 4000:4000
links:
- db
db:
container_name: mongodb
image: mongo:latest
volumes:
- ./data-volume:/data/db
ports:
- 27017:27017
Here as you can see from the file few things are already known to you. Here we will talk about remaining three properties ‘image’ and ‘volumes’ under ‘db’ service and ‘links’ property under ‘app’ serivce
‘volumes’ : Volumes are persistent data stores implemented by the container engine. Compose offers a neutral way for services to mount volumes, and configuration parameters to allocate them to infrastructure. Here we want the data stored in MongoDB to persistent and not get lost on every start . So in order to make it persistent data-volume has been mapped to data/db
‘image’ : This signifies the image of the particular service
‘links’: It instructs Docker to link containers over a network. When we link containers, Docker creates environment variables and adds containers to the known hosts list so they can discover each other.
Step-10 Connect to MongoDB from Node.js/Typescript app
Create file dbconnect.ts in the root of the project directory and write the following code inside it
import mongoose from "mongoose";
export async function connect(){
try{
await mongoose.connect("mongodb://db:27017/mydatabase");
console.log(`Sucessfully Connected`)
}catch(err:any){
console.log(`Some problem happened while connecting to db ${err.message}`);
}
}
Step-11 Import the connection file in app.ts and call
Now we are going to call the connect function from app.ts . For that we need to add some code to our existing app.ts file. After importing and adding the function to app,ts the modified code should look like the following
import express, {Application} from 'express'
import { connect } from './dbconnect';
const app:Application = express();
const PORT = 4000
app.use(express.json());
(async()=>
await connect()
)();
app.listen(PORT,()=>{
console.log(`Server started at port ${PORT}`)
})
Step-12 Finally test the application with MongoDB attached
To run the app through docker-compose first destroy the previous containers by issuing the following command
docker-compose down --remove-orphans
After the container are stopped and destroyed run the following command to run it once again
docker-compose up --build
Now if you see these two messages in the console
‘Server started at port 4000’
‘Successfully connected’.
That means you have able to successfully configure the application to run in docker