DEV Community

Cover image for ExpressJS, MongoDB and React on Docker containers
Ștefan Vîlce
Ștefan Vîlce

Posted on • Edited on

ExpressJS, MongoDB and React on Docker containers

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

npm install --save express
npm install --save body-parser
Enter fullscreen mode Exit fullscreen mode

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

// 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
})
Enter fullscreen mode Exit fullscreen mode

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.

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

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

We have to replace localhost with host.docker.internal. So, the connection function will this one:

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

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

node_modules
.git
Dockerfile
Enter fullscreen mode Exit fullscreen mode

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

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();
  }
});
Enter fullscreen mode Exit fullscreen mode

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


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

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

docker run --name mongodbcontainer --rm -v data:/data/db -p 27017:27017 mongo
Enter fullscreen mode Exit fullscreen mode

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

docker run --name mongodbcontainer --rm -v //c/Users/UserName/Documents/Proiects/docker_express_react_mongo/mongo/data:/data/db -p 27017:27017 mongo
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

or, in cmd Windows you’ll write:

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 
Enter fullscreen mode Exit fullscreen mode

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

Image description

Image description

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

Create the ReactApp

You can create the React app

npx create-react-app reactapp
cd reactapp
npm start
Enter fullscreen mode Exit fullscreen mode

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

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;
Enter fullscreen mode Exit fullscreen mode

.dockerignore

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

Dockerfile

This file will contain:

FROM node
WORKDIR /app
COPY reactapp/package*.json ./
RUN npm install
COPY ./reactapp .
EXPOSE 3000
CMD ["npm", "start"]
Enter fullscreen mode Exit fullscreen mode

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:

# 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:
Enter fullscreen mode Exit fullscreen mode

Bibliografy


GitHub Repo

The entire project can be found here:

https://github.com/stefanvilce/docker_mongo_react_express


Top comments (0)