docker
It is the simple blueprint of what we are going to build over this article
To make thing easier, we will need two containers
docker pull mongo
docker pull mongo-express
Now we have both mongodb & mongo-express images
We can use docker network in which our application containers interact with each other by its container name
// create a new mongo network
docker network create mongo-network
// than we can check the newly created network
docker network ls
We can use docker run to cmd to run the container. Before that, we will need to define some environment varables. We can refer to the official doc to see the detailed variables.
We are just going to define the most important two:
MONGO_INITDB_ROOT_USERNAME
MONGO_INITDB_ROOT_PASSWORD
// name is container name, it is important becase we will need the name to connect to mongo-express
docker run -d \
--net mongo-network \
--name mongodb \
-e MONGO_INITDB_ROOT_USERNAME=admin \
-e MONGO_INITDB_ROOT_PASSWORD=admin \
-p 27017:27017 \
mongo
Now we should see our mongodb container runing by checking docker ps
We can also see the detailed info in the official doc
There is a issue in newest version of mongo-express container setting. we need to use following env variable instead of the one defined in official doc. See the issue here
docker run -d\
--name mongo-express \
--net mongo-network \
-p 8081:8081 \
-e ME_CONFIG_MONGODB_URL=mongodb://admin:admin@mongodb:27017/ \
mongo-express
then we can check if it is running good by docker logs <contianer id>
Server is open to allow connections from anyone (0.0.0.0)
basicAuth credentials are "admin:pass", it is recommended you change this in your config.js!
When we see the logs above, and we now are able to access the mongo-express by the following url:
How we have two docker containers runing and we can also connect the mongodb by mongo-express ui contianer thorugh our browser.
Now we need to, on the other hand, connect the mongodb with our frontend app that we just created.
We will need to create a new database called "user-account". Then create one collection called "users". And we are ready to go.
Our data object format is like below:
{
_id: ObjectId('6252a0ded62f5286e3f7187d'),
userid: 1,
name: 'eric lee',
email: 'Colorlife@gmail.com',
interests: 'code and life'
}
In our server code, we add the logic to connect the database.
import express from "express";
import path from "path";
import fs from "fs";
import { MongoClient } from "mongodb";
import bodyParser from "body-parser";
let app = express();
// Render static (CSS & JS) files from root folder
app.use(express.static(__dirname));
app.use(
bodyParser.urlencoded({
extended: true,
})
);
app.use(bodyParser.json());
app.get("/", function (req, res) {
res.sendFile(path.join(__dirname, "index.html"));
});
app.get("/profile-picture", function (req, res) {
let img = fs.readFileSync(path.join(__dirname, "images/profile-1.jpg"));
res.writeHead(200, { "Content-Type": "image/jpg" });
res.end(img, "binary");
});
let mongoUrlLocal = "mongodb://admin:admin@localhost:27017";
// use when starting application as docker container
let mongoUrlDocker = "mongodb://admin:admin@mongodb";
// "user-account" in demo with docker. "my-db" in demo with docker-compose
let databaseName = "user-account";
app.post("/update-profile", function (req, res) {
let userObj = req.body;
try {
MongoClient.connect(
mongoUrlLocal,
// mongoClientOptions,
(err, client) => {
if (client) {
let db = client.db(databaseName);
userObj["userid"] = 1;
let query = { userid: 1 };
let newvalues = userObj;
let collection = db?.collection("users");
collection.updateOne(query, { $set: newvalues }, (err, result) => {
if (!err) {
console.log(result);
client.close();
}
});
}
}
);
res.status(200);
res.send(userObj);
} catch (e: any) {
res.status(404);
res.send(`Error connecting database: ${JSON.stringify(e)}`);
}
res.send("WIP: update-profile");
});
app.get("/get-profile", function (req, res) {
// Connect to the db
try {
MongoClient.connect(mongoUrlLocal, async (err, client) => {
let db = client?.db(databaseName);
let query = { userid: 1 };
let collection = db?.collection("users");
let result = await collection?.findOne(query);
res.send(result);
});
} catch (e) {
res.status(404);
res.send(`Error connecting database: ${JSON.stringify(e)}`);
}
});
app.listen(3000, function () {
console.log("app listening on port 3000!");
});
In our app.ts code, we also add some logic for querying mongodb database
async function handleUpdateProfileRequest() {
try {
const contEdit = document.getElementById("container-edit")!;
const cont = document.getElementById("container")!;
const payload = {
name: (document.getElementById("input-name")! as HTMLInputElement).value,
email: (document.getElementById("input-email")! as HTMLInputElement)
.value,
interests: (
document.getElementById("input-interests")! as HTMLInputElement
).value,
};
const response = await fetch("http://localhost:3000/update-profile", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
const jsonResponse = await response.json();
document.getElementById("name")!.textContent = jsonResponse.name;
document.getElementById("email")!.textContent = jsonResponse.email;
document.getElementById("interests")!.textContent = jsonResponse.interests;
cont.style.display = "block";
contEdit.style.display = "none";
} catch (e) {
console.log(e);
}
}
function updateProfile() {
const contEdit = document.getElementById("container-edit")!;
const cont = document.getElementById("container")!;
(document.getElementById("input-name")! as HTMLInputElement).value =
document.getElementById("name")!.textContent ?? "";
(document.getElementById("input-email")! as HTMLInputElement).value =
document.getElementById("email")!.textContent ?? "";
(document.getElementById("input-interests")! as HTMLInputElement).value =
document.getElementById("interests")!.textContent ?? "";
cont.style.display = "none";
contEdit.style.display = "block";
}
(async function init() {
const response = await fetch("http://localhost:3000/get-profile");
const user = await response.json();
document.getElementById("name")!.textContent = user.name
? user.name
: "ColorfulLife.jp";
document.getElementById("email")!.textContent = user.email
? user.email
: "colorfulLife@example.com";
document.getElementById("interests")!.textContent = user.interests
? user.interests
: "coding, enjoying life";
const cont = document.getElementById("container")!;
cont.style.display = "block";
})();
Now we can see our data updated from database already.
Instead of docker run all the related contianers and create the network manually, docker-compose makes thing much easier.
We can run simply one command to activate all needed containers inside one docker network just simply create one docker compose config file.
version: "3.8"
services:
mongodb:
image: mongo
ports:
- 27017:27017
environment:
- MONGO_INITDB_ROOT_USERNAME=admin
- MONGO_INITDB_ROOT_PASSWORD=admin
mongo-express:
image: mongo-express
restart: always # fixes MongoNetworkError when mongodb is not ready when mongo-express starts
ports:
- 8080:8081
environment:
- ME_CONFIG_MONGODB_URL=mongodb://admin:admin@mongodb:27017/
You mignt noice that we do not define "name" & "net" becase docker will handle itself.
then, run following command and everything working properly:
// start up all containers
docker-compose -f docker-compose.yaml up -d
// stop all containers
docker-compose -f docker-compose.yaml down
Now to make things more easy, we can make our own application as a sigle docker compainer by docker build command. Before that, we will need to create a Dockerfile
FROM node:13-alpine
ENV MONGO_DB_USERNAME=admin \
MONGO_DB_PWD=admin
# The RUN command will be run inside the container, not host
RUN mkdir -p /home/app
# The COPY command can copy host content to docker container
COPY ./app /home/app
# set default dir so that next commands executes in /home/app dir
WORKDIR /home/app
# will execute npm install in /home/app because of WORKDIR
RUN npm install
RUN npm run build
# no need for /home/app/server.js because of WORKDIR
CMD ["npm", "run", "start"]
There is one more thing need to remember is that when we use this application as a container, it is run inside the docker network instead of local network. So, we will need to change the mongodb connection code insdie our server.ts code to make sure it only makes use of the name of the container
// use when starting application as docker container
let mongoUrlDocker = "mongodb://admin:admin@mongodb";
Then we can run the following command to build our own docker image
docker build . -t my-app
Now our application also becomes a contianer, so we can include it into our docker compose file.
version: "3.8"
services:
my-app:
image: my-app
restart: always
ports:
- 3000:3000
mongodb:
image: mongo
restart: always
ports:
- 27017:27017
environment:
- MONGO_INITDB_ROOT_USERNAME=admin
- MONGO_INITDB_ROOT_PASSWORD=admin
mongo-express:
image: mongo-express
restart: always # fixes MongoNetworkError when mongodb is not ready when mongo-express starts
ports:
- 8080:8081
environment:
- ME_CONFIG_MONGODB_URL=mongodb://admin:admin@mongodb:27017/
Finally, we only need to run one command to start our application
docker-compose -f docker-compose.yaml up -d
The drawing below is what our application now looks like:
See all the sample code from my git repo here