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
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
})
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 });
}
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 });
}
Create .dockerignore
file. It works as .gitignore
. In this file we have to write
node_modules
.git
Dockerfile
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();
}
});
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
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:
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
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
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
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
In this MongoDB let’s create the first schema (Stefan) and the first collection (User)
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.
Create the ReactApp
You can create the React app
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
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" />
<input ref={inputEmail} type="text" placeholder="Email of this user" />
<button>Save User</button>
<p> </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:
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:
# 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://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://expressjs.com/
- https://create-react-app.dev/docs/getting-started/
- https://www.mongodb.com/
GitHub Repo
The entire project can be found here:
https://github.com/stefanvilce/docker_mongo_react_express
Top comments (0)