###### tags: `docker` # Use Typescript + Docker to create a full-stake web application It is the simple blueprint of what we are going to build over this article ![](https://i.imgur.com/e7e22pT.png) ## Local Frontend express application ![](https://i.imgur.com/caTA96l.png) ## Get backend db working with official mongodb container To make thing easier, we will need two containers 1. [mongodb](https://hub.docker.com/_/mongo): this is the official container of mongodb database 2. [mongo-express](https://hub.docker.com/_/mongo-express): this is the express ui to let us to manipulate the db easier. ![](https://i.imgur.com/0tTlUWd.png) ``` docker pull mongo docker pull mongo-express ``` Now we have both mongodb & mongo-express images ### Mongo Network ![](https://i.imgur.com/f59D2EA.png) 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 ``` ### Run mongoDB & mongdb-express container #### Mongodb 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](https://hub.docker.com/_/mongo) to see the detailed variables. We are just going to define the most important two: > MONGO_INITDB_ROOT_USERNAME > MONGO_INITDB_ROOT_PASSWORD ![](https://i.imgur.com/2MqlYaM.png) ``` // 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``` #### Mongo-express We can also see the detailed info in the [official doc](https://hub.docker.com/_/mongo-express) ![](https://i.imgur.com/uX3aPO9.png) > 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](https://github.com/mongo-express/mongo-express/issues/720#issuecomment-871040716) ``` 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: > http://localhost:8181 ### Connect mongodb with our local express app 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. ![](https://i.imgur.com/pH4jgcU.png) 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. ## Docker compose 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 ``` ## merge our application with those two contianer to build a new docker image. 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: ![](https://i.imgur.com/g0zR2iS.png) See all the sample code from my git repo [here](https://github.com/happyeric77/docker_express_mogodb_typescript.git)