ExpressJS, MongoDB and React on Docker containers
Tags: docker, javascript, react, express
Published: 2023-03-30 09:12:47

# Installation on Docker and run on Docker containers

We start with the presumption that:

- you have already installed the **nodejs** and **npm**.
- you have already installed **Docker**.

# Create the folder where keep all 3 apps

Let’s create the folder where the 3 applications (backend, frontend, mongodb) will be kept: docker_express_react_mongo

Create the three folders inside:

- **backend**
- **frontend**
- **mongo**

# Express App / **backend** folder. Port 8080

Go to **backend** folder:  `cd backend`

Now you have to install  the Express app: `npm init`

```bash
npm install --save express
npm install --save body-parser
```

Now create **index.js** file. The index.js file should contain this: 

```jsx
// Entry Point of the API Server
const express = require('express');

/* Creates an Express application.
   The express() function is a top-level
   function exported by the express module.
*/
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }));

app.get('/', (req, res)=>{
    res.set('Content-Type', 'text/html');
    res.status(200).send("<h1>Welcome!</h1><p>This is the Express Api App.</p>");
});

app.get('/test', (req, res) => {
    console.log("TEST API Express");
	res.set('Content-Type', 'text/json');
	res.status(200).send("[{\"text\": 23}]");
});

// Require the Routes API
// Create a Server and run it on the port 8080
const server = app.listen(8080, function () {
    let host = server.address().address
    let port = server.address().port
    // Starting the Server at the port 8080
})
```

### Starting the backend container

docker build -t express-image-02 . 

docker run --name express-container-02 --rm -p 8080:8080 express-image-02

### Back to code in backend/ExpressJS app after we setup MongoDB

We have put in place the database, i.e. MongoDB. Now we have to get back to our code and continue with this nodeJS app.  

```jsx
async function connectare(){
return await MongoClient.connect('mongodb://root:pass123@localhost:27017/?authMechanism=DEFAULT&authSource=admin',  { useNewUrlParser: true, useUnifiedTopology: true });
}
```

But MongoDB runs inside a Docker container, so it is unnaccessible through localhost. 

We have to replace [localhost](http://localhost) with **host.docker.internal**. So, the connection function will this one:

```jsx
async function connectare(){
  return await MongoClient.connect('mongodb://root:pass123@host.docker.internal:27017/?authMechanism=DEFAULT&authSource=admin',  { useNewUrlParser: true, useUnifiedTopology: true });
}
```

---

Create `.dockerignore`  file. It works as  `.gitignore` . In this file we have to write

```bash
node_modules
.git
Dockerfile
```

---

Now, let’s see how the code in backend/index.js looks like:

```jsx
const express = require('express');
const MongoClient = require('mongodb').MongoClient;
const PORT = 8080;
const  ObjectId = require('mongodb').ObjectId;

const bodyParser = require('body-parser');
var cors = require("cors");

const app = express();
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());
app.use(cors({
  origin: 'http://localhost:3000'
}));

async function connectare(){
  return await MongoClient.connect('mongodb://root:pass123@host.docker.internal:27017/?authMechanism=DEFAULT&authSource=admin',  { useNewUrlParser: true, useUnifiedTopology: true });
}

console.log("We are connected!");

app.listen(PORT, () => {
  console.log('Server listening on port ' + PORT);
});

// ROUTES

app.get('/', (req, res)=>{
    let contentIndex = "<h1>Hello!</h1>Dette er REST API serveren, med Express. Test <a href='/usersbyclient'>here</a>!";
    contentIndex = contentIndex + "<p>The connection with MongoDB is up and running.</p>";
    res.set('Content-Type', 'text/html');
    res.send(contentIndex);
});

app.get('/users', async (req, res) => {
  /*
   * Requires the MongoDB Node.js Driver
   * https://mongodb.github.io/node-mongodb-native
   */
  const filter = {};
  const client = await connectare();
  const coll = client.db('Stefan').collection('User');
  const cursor = coll.find(filter);
  const result = await cursor.toArray();
  await client.close();
  res.json(result);
});

app.get('/user/search/:word', async (req, res) => {
  const filter = { 'name': new RegExp(req.params.word) };
  const projection = {
   _id: 0,
   name: 1,
 };
  const client = await connectare();
  const coll = client.db('Stefan').collection('User');
  const cursor = coll.find(filter).project(projection);
  //const cursor = coll.find(filter);
  const result = await cursor.toArray();
  await client.close();
  res.json(result);
});

app.get('/user/:id', async (req, res) => {
  const filter = {_id: new ObjectId(req.params.id)};
  const client = await connectare();
  const coll = client.db('Stefan').collection('User');
  const cursor = coll.find(filter);
  const result = await cursor.toArray();
  await client.close();
  res.json(result);
});

app.get('/user/name/:name', async (req, res) => {
  const filter = { 'name': req.params.name };
  const client = await connectare();
  const coll = client.db('Stefan').collection('User');
  const cursor = coll.find(filter);
  const result = await cursor;
  await client.close();
  res.json(result);
});

app.post('/user/add', async (req, res) => {
  var name = req.body.name;
  var email = req.body.email;
  const client = await connectare();
  const coll = client.db('Stefan').collection('User');
  try {
    await coll.insertOne({
      name: name,
      email: email
    });
    res.sendStatus(204);
  } catch (error){
    throw res.status(500).json({message: "Server error occured!"});
  } finally {
    await client.close();
  }
});

app.patch('/user/update/:id', async (req, res) => {
  const query = { _id: new ObjectId(req.params.id) };
  var name = req.body.name;
  var email = req.body.email;
  const updates = {
    $set: { name: name, email: email }
  };
  const client = await connectare();
  const coll = client.db('Stefan').collection('User');
  try {
    console.log("Se modifica obiectul : " + query);
    console.log("Se aduc modificarile astea: " + JSON.stringify(updates));
    await coll.updateOne(query, updates);
    res.status(204).json({status:"Success", message: "The User was successfully updated."});
  } catch (error){
    throw res.status(500).json({message: "Server error occured!"});
  } finally {
    await client.close();
  }
});

app.delete('/user/delete/:id', async (req, res) => {
  const filter = {_id: new ObjectId(req.params.id)};
  const client = await connectare();
  const coll = client.db('Stefan').collection('User');
  try {
    await coll.deleteOne(filter);
    res.status(200).send({status: "Success", message: "The User has been deleted. User id: " + req.params.id + ". "});
  } catch (error){
    throw res.status(500).json({message: "Server error occured!"});
  } finally {
    await client.close();
  }
});
```

# MongoDB / mongo folder

---

The database should run on PORT: **27017**.

First, go to  docker_express_react_mongo folder (*parent folder*) and create a new folder, **mongo**.

`mkdir mongo` and then `cd mongo`

Inside the **mongo** folder  we create another folder, **data,** where the data will be syncronized with the data inside container. This way the data will be persistent.

First, check if mongo is installed: `docker run mongo`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4yxae6xq5m87vxel4qpd.png)

---

This will **fetch** mongo from **Docker Hub**. If you don't have it, then it will run a container based on a **mongo** image.

Run `docker ps`  and you’ll get something like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dj6n0na7jnfmmqkp44d6.png)

It means that the mongo container is running. Now we run the MongoDB container taking care that the data persists; otherwise you’ll lose the data every time when you stop the container. You need to add the volume here with this sequence  `-v data:/data/db` 

```powershell
docker run --name mongodbcontainer --rm -v data:/data/db -p 27017:27017 mongo
```

But, if you are working in Windows you should put the entire path for your local volume, such as:

```powershell
docker run --name mongodbcontainer --rm -v //c/Users/UserName/Documents/Proiects/docker_express_react_mongo/mongo/data:/data/db -p 27017:27017 mongo
```

This database has no username and no password. If you want a database with no security issues then you have to add **-e MONGO_INITDB_ROOT_USERNAME=root -e MONGO_INITDB_ROOT_PASSWORD=pass123** and you’ll setup the username as **root** and the password as **pass123**.

```powershell
docker run --name mongodbcontainer --rm -v data:/data/db -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=root -e MONGO_INITDB_ROOT_PASSWORD=pass123 mongo
```

or, in cmd Windows you’ll write:

```powershell
docker run --name mongodbcontainer --rm -v //c/Users/UserName/Documents/Proiects/docker_express_react_mongo/mongo/data:/data/db -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=root -e MONGO_INITDB_ROOT_PASSWORD=pass123 mongo 
```


In this MongoDB let’s create the first schema (**Stefan**) and the first collection (**User**)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i3dxrx1p9p0c8zovzj82.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p182s7osr6dxojs7jnvq.png)


## Access the database container

If you want to access the Dabase Container (or every other container in Docker) you have to run the command:

`docker exec -it <CONTAINER_NAME> bash` 

In your/this case, we you have to run 

`docker exec -it mongodbcontainer bash` 



# FrontEnd - ReactApp. Port 3000

Create a new folder called **frontend**. Inside this folder create a new file **Dockerfile**. The same way as for **backend**. And then a new file called **.dockeignore** and **.gitignore**. 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cu7rppxl3kc15quz1n9i.png)

### Create the ReactApp

You can create the React app

```bash
npx create-react-app reactapp
cd reactapp
npm start
```

And the folder **reactapp** will be created. The app will run on port 3000. 

After you create the app and after you MongoDB is up and the backend is changed you can add this code to your App.js

```jsx
import "./App.css";
import { useRef, useState, useEffect } from "react";

function App() {
  const inputName = useRef("");
  const inputEmail = useRef("");
  const apiUrl = "http://localhost:8080";
  const [users, setUsers] = useState([]);

  useEffect(() => {
    async function fetchUsers() {
      const res = await fetch(`${apiUrl}/users`);
      const data = await res.json(); console.log("This is the data coming from API: " + JSON.stringify(data, 2,0));
      setUsers(data);
    }
    fetchUsers();
  }, []);

  function onSubmit(e) {
    e.preventDefault();
    addNewUser();
  }

  function addNewUser() {
    const name = inputName.current.value;
    const email = inputEmail.current.value;
    fetch(`${apiUrl}/user/add`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ name: name, email: email }),
    }).then(async (response) => {
      if(response.status == 204){
        const res = await fetch(`${apiUrl}/users`);
        const data = await res.json();
        setUsers(data);
        inputName.current.value = "";
        inputEmail.current.value = "";
      } else {
        console.log("We cannot save the User.");
      }
    });
  }

  
  return (
    <div className="App">
      <header className="App-header">
        <p>Users App</p>
        <UsersList users={users} />
      </header>
      <form  onSubmit={onSubmit}>
        <input ref={inputName} type="text" placeholder="Name of user" />
        &nbsp; 
        <input ref={inputEmail} type="text" placeholder="Email of this user" />
        &nbsp; <button>Save User</button>
        <p> &nbsp; </p>
      </form>      
    </div>
  );
}

// using object destructuring on the props object
function UsersList({ users }) {
  return (
    <ul>
      {users.map((user) => (
        <li key={user._id}>{user.name}, email: {user.email}</li>
      ))}
    </ul>
  );
}

export default App;
```

### .dockerignore

It will be built the same way as in the **backend** folder.

### Dockerfile

This file will contain:

```bash
FROM node
WORKDIR /app
COPY reactapp/package*.json ./
RUN npm install
COPY ./reactapp .
EXPOSE 3000
CMD ["npm", "start"]
```

Create the ReactApp inside the **reactapp** folder. 

# docker-compose up

## Wrapping up all three containers

Go out of **frontend** folder. And create a new file, **docker-compose.yaml**. This file should contain this code:

```bash
# docker compose version which is currently 3.8
version: "3.8"

# services : is a list of our container
services:
  # name is optional for our mongodb
  mymongodb:
    # since mongo is an offical image we can use it.
    image: "mongo"

    # the port that we want to publish for mongodb
    ports:
      - "27017:27017"

    # our mongodb depends on volume to keep the data alive.
    volumes:
      - ./mongo/data:/data/db

    # our environment variable
    environment:
      MONGO_INITDB_ROOT_USERNAME: "root"
      MONGO_INITDB_ROOT_PASSWORD: "pass123"

  # name is optional for our backend
  backend:
    # to build an image based on Dockerfile
    # it looks in this folder to find Dockerfile to build an image
    build: ./backend
    # the port that we want to publish for backend
    ports:
      - "8080:8080"

    # depends_on means it will start our backend container once mongo-container is  up and running.
    depends_on:
      - mymongodb

  # name is optional for our frontend
  frontend:
    # to build an image based on Dockerfile
    # it looks in this folder to find Dockerfile to build an image
    build: ./frontend

    # the port that we want to publish for frontend
    ports:
      - "3000:3000"
    # add bind mount volume to keep have updated source code
    volumes:
      - ./frontend/reactapp/src:/app/src
    # allow interactive mode
    stdin_open: true
    tty: true

    # it will start our frontend container once backend-container is  up and running.
    depends_on:
      - backend

# declare the volumes name that our app is using.
volumes:
  data:
  src:
```

# Bibliografy

---

- [Docker with Express,  ReactJS and MongoDB](https://medium.com/@Anas.shahwan/dockerizing-nodejs-react-js-and-mongodb-apps-be67a73c7a7b#id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjU5NjJlN2EwNTljN2Y1YzBjMGQ1NmNiYWQ1MWZlNjRjZWVjYTY3YzYiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJuYmYiOjE2NzYyOTE3NTksImF1ZCI6IjIxNjI5NjAzNTgzNC1rMWs2cWUwNjBzMnRwMmEyamFtNGxqZGNtczAwc3R0Zy5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsInN1YiI6IjEwMjY4OTM4ODYwMTk0MzM1NDQyNCIsImhkIjoiaXJpc29mdC5jb20ucm8iLCJlbWFpbCI6InN0ZWZhbkBpcmlzb2Z0LmNvbS5ybyIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhenAiOiIyMTYyOTYwMzU4MzQtazFrNnFlMDYwczJ0cDJhMmphbTRsamRjbXMwMHN0dGcuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJuYW1lIjoiyJh0ZWZhbiBWw65sY2Vsb2l1IiwicGljdHVyZSI6Imh0dHBzOi8vbGgzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9hL0FFZEZUcDZTTUxFMURQU3c2TUZnZHVQRmx3c2tXQk8yVnB0SlFNM281QVBaSHc9czk2LWMiLCJnaXZlbl9uYW1lIjoiyJh0ZWZhbiIsImZhbWlseV9uYW1lIjoiVsOubGNlbG9pdSIsImlhdCI6MTY3NjI5MjA1OSwiZXhwIjoxNjc2Mjk1NjU5LCJqdGkiOiJjOGU0MzJlOTA1OGI5YmM3MzdkNmM1ZmNhNDFlMDRhM2VlMGYyNDM0In0.Do6Y1gMdaYf148s_F4CsC15lOtyJyPf_M0iV8ww-kvx-2ZUH3CutTBVMT4mgtT5NC9eSvj2JH8Ss3UDKYAWKMSbp1G-_ytGL7ORn5LybPSRcEbQcnl3ykkfh0Za40mUOhz9uZRe5SmDAVRX9ev8JOjmtZENzeRnLVlRSexCM3wuBjPZoiqMY8ilwzbAlxi3O-T5WlX4aWo2hciyVvE7M5O8ddwtHyWA2XD_YrikS7HLGbFtuRezy3XVSiDd9kle2PUQTnMdFIOMpKiFvsQeEfFZlzynAv75YFm3ZDB4c4Wii_upu5xM9VfK1L0K3Baa32zUFJr6aDJaQvmwEBycBFw)
- [https://www.geeksforgeeks.org/creating-a-rest-api-backend-using-node-js-express-and-postgres/](https://www.geeksforgeeks.org/creating-a-rest-api-backend-using-node-js-express-and-postgres/)
- [https://www.mongodb.com/languages/express-mongodb-rest-api-tutorial](https://www.mongodb.com/languages/express-mongodb-rest-api-tutorial)
- [https://expressjs.com/](https://expressjs.com/)
- [https://create-react-app.dev/docs/getting-started/](https://create-react-app.dev/docs/getting-started/)
- [https://www.mongodb.com/](https://www.mongodb.com/)

# GitHub Repo

The entire project can be found here: 

[https://github.com/stefanvilce/docker_mongo_react_express](https://github.com/stefanvilce/docker_mongo_react_express)

---