The MERN stack (MongoDB, Express.js, React, Node.js) is a powerful technology stack for building modern web applications. In this blog post, we’ll walk through the process of creating a simple Todo application using the MERN stack and then dockerizing it for easy deployment and scalability.
A Closer Look at MERN Stack Components
MongoDB: A cross-platform document database
MongoDB is a NoSQL (non-relational) document-oriented database. Data is stored in flexible documents with a JSON (JavaScript Object Notation)-based query language. MongoDB is known for being flexible and easy to scale.
Express: A back-end web application framework
Express is a web application framework for Node.js, another MERN component. Instead of writing full web server code by hand on Node.js directly, developers use Express to simplify the task of writing server code.
React: A JavaScript library for building user interfaces
React was originally created by a software engineer at Facebook, and was later open-sourced. The React library can be used for creating views rendered in HTML. In a way, it’s the defining feature of the stack.
Node.js: A cross-platform JavaScript runtime environment
Node.js is constructed on Chrome’s V8 JavaScript engine. It’s designed to build scalable network applications and can execute JavaScript code outside of a browser.
Prerequisites
Before we start, ensure you have the following installed on your machine:
Node.js and npm
MongoDB
Docker
Step 1: Setting Up the Project
First, create a directory for your project and initialize it:
mkdir merntodo
cd merntodo
Step 2: Creating the Backend with Node.js and Express
Initialize the backend:
mkdir server
cd server
npm init -y
npm install express mongoose cors body-parser
Create a basic Express server (server/server.js):
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const bodyParser = require('body-parser');
const app = express();
const PORT = process.env.PORT || 5000;
app.use(cors());
app.use(bodyParser.json());
const dbURI = process.env.MONGO_URI || 'mongodb://localhost:27017/mern-todo';
mongoose.connect(dbURI, {
useNewUrlParser: true,
useUnifiedTopology: true
}).then(() => console.log('MongoDB connected'))
.catch(err => console.log(err));
const todoRoutes = require('./routes/todo');
app.use('/api', todoRoutes);
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
use your db URL in const dbURI = process.env.MONGO_URI ||‘mongodb://localhost:27017/mern-todo’;
Define the Todo model (server/models/Todo.js):
const mongoose = require('mongoose');
const todoSchema = new mongoose.Schema({
text: {
type: String,
required: true
},
completed: {
type: Boolean,
default: false
}
});
module.exports = mongoose.model('Todo', todoSchema);
Create routes for the Todo API (server/routes/todo.js):
const express = require('express');
const router = express.Router();
const Todo = require('../models/Todo');
// Get all todos
router.get('/todos', async (req, res) => {
try {
const todos = await Todo.find();
res.json(todos);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
// Create a new todo
router.post('/todos', async (req, res) => {
const todo = new Todo({
text: req.body.text
});
try {
const newTodo = await todo.save();
res.status(201).json(newTodo);
} catch (err) {
res.status(400).json({ message: err.message });
}
});
// Delete a todo
router.delete('/todos/:id', async (req, res) => {
try {
const todo = await Todo.findById(req.params.id);
if (!todo) {
return res.status(404).json({ message: 'Todo not found' });
}
await todo.remove();
res.json({ message: 'Todo deleted' });
} catch (err) {
res.status(500).json({ message: err.message });
}
});
module.exports = router;
Step 3: Creating the Frontend with React
Initialize the frontend:
npx create-react-app client
cd client
Create a Todo List component (client/src/TodoList.js):
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const TodoList = () => {
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState('');
useEffect(() => {
fetchTodos();
}, []);
const fetchTodos = async () => {
try {
const res = await axios.get('http://localhost:5000/api/todos');
setTodos(res.data);
} catch (err) {
console.error(err);
}
};
const createTodo = async () => {
try {
const res = await axios.post('http://localhost:5000/api/todos', { text: newTodo });
setTodos([...todos, res.data]);
setNewTodo('');
} catch (err) {
console.error(err);
}
};
const deleteTodo = async (id) => {
try {
await axios.delete(`http://localhost:5000/api/todos/${id}`);
setTodos(todos.filter(todo => todo._id !== id));
} catch (err) {
console.error(err);
}
};
return (
<div>
<h1>Todo List</h1>
<input
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
/>
<button onClick={createTodo}>Add Todo</button>
<ul>
{todos.map(todo => (
<li key={todo._id}>
{todo.text}
<button onClick={() => deleteTodo(todo._id)}>Delete</button>
</li>
))}
</ul>
</div>
);
};
export default TodoList;
Update client/src/App.js to include the Todo List component:
import React from 'react';
import TodoList from './TodoList';
function App() {
return (
<div className="App">
<TodoList />
</div>
);
}
export default App;
Step 4: Dockerizing the Application
Create Dockerfiles
Backend Dockerfile (server/Dockerfile):
# Use the official Node.js image as the base image
FROM node:14
# Create and set the working directory
WORKDIR /usr/src/app
# Copy package.json and package-lock.json to the working directory
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy the rest of the application code
COPY . .
# Expose the port the app runs on
EXPOSE 5000
# Start the application
CMD ["node", "server.js"]
Frontend Dockerfile (client/Dockerfile):
# Use the official Node.js image as the base image
FROM node:14
# Create and set the working directory
WORKDIR /usr/src/app
# Copy package.json and package-lock.json to the working directory
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy the rest of the application code
COPY . .
# Build the React app
RUN npm run build
# Install serve to serve the production build
RUN npm install -g serve
# Expose the port the app runs on
EXPOSE 3000
# Start the application
CMD ["serve", "-s", "build"]
Create Docker Compose File (docker-compose.yml)
version: '3.8'
services:
backend:
build:
context: ./server
dockerfile: Dockerfile
ports:
- "5000:5000"
environment:
- NODE_ENV=development
- MONGO_URI=mongodb://mongo:27017/mern-todo
depends_on:
- mongo
frontend:
build:
context: ./client
dockerfile: Dockerfile
ports:
- "3000:3000"
depends_on:
- backend
mongo:
image: mongo:4.2
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db
volumes:
mongo-data:
Step 5: Build and Run Docker Containers
From the root directory of your project, run the following commands to build and start your Docker containers:
docker-compose build
docker-compose up
Step 6: Access Your Application
Frontend: Open your browser and navigate to http://localhost:3000
Backend: The backend API will be running at http://localhost:5000
Conclusion
By following these steps, you have successfully built a MERN stack Todo application and dockerized it for easy deployment. Dockerizing your application ensures that it runs consistently across different environments and simplifies the deployment process. Happy coding!
code repo:github.com/PRATIKNALAWADE/Docker-mern