DEV Community

Cover image for Building a Calorie Journal SaaS based project using MERN stack πŸ”₯
Pramit Marattha for Aviyel Inc

Posted on • Updated on • Originally published at aviyel.com

Building a Calorie Journal SaaS based project using MERN stack πŸ”₯

This blog article concentrates on the most significant tasks and ideas to assist you in better understanding and building MERN stack applications from the absolute ground up. It is intended for folks who are really curious about the MERN stack and want to focus on what they really need to know.

So, What is the MERN stack?

The MERN stack is a popular technology stack for creating modern Single Page Applications also known as SPA in short. MongoDB, Express, React, and Node.js are the acronyms for the β€œMERN” stack.MERN is a variant of the very popular MEAN stack (MongoDB, Express, Angular, Node), with React replacing Angular as the frontend UI framework. The MEVN (MongoDB, Express, Vue, Node), which uses Vue as the frontend UI framework, is another very popular option. These frontends tech stack helps for building Single Page Applications (SPAs) which helps to avoid reloading the entire page and only fetch relevant pieces of information of the page from the server and displays freshly and newly updated stuff.

MERN

MERN stack

In this blog article, we'll build a full-stack calorie tracker application that users can use to keep track of users food habits and are able to track their entire calorie count by utilizing the absolute power of the MERN stack only. This blog tutorial should help you understand the fundamentals as well as advanced concepts and operations of the MERN stack technology. Here is our application's final sneak peek.

Demo

Demo

Demo

Configuring our folder structure

Create a two folder name client and server inside your project directory, then open it inside the Visual Studio Code or any code editor of your choice.

Folder Structure

Folder Structure

Now we'll set up our backend with npm and install the required packages, then configure a MongoDB database, set up a server with Node and Express, establish a database schema to describe our calorie tracker application, and set up API routes to create, read, update, and delete data and information from the database. So, using a command prompt, navigate to your server's directory and run the code below.

npm init -y
Enter fullscreen mode Exit fullscreen mode

Configuring and updating our package.json file

Execute the following commands in the terminal to install the dependencies.

npm install cors dotenv express mongoose nodemon body-parser
Enter fullscreen mode Exit fullscreen mode

Dependencies

Installing Dependencies

The "package.json" file should look like this after the dependencies have been installed.

{
  "name": "server",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "start": "nodemon app.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.19.0",
    "cors": "^2.8.5",
    "dotenv": "^10.0.0",
    "express": "^4.17.1",
    "mongoose": "^6.0.13",
    "nodemon": "^2.0.15"
  }
}
Enter fullscreen mode Exit fullscreen mode

Also, don't forget to update the scripts.

Start Script

Now go to your server directory and make a app.js file there.
The structure of your folders and file should resemble this.

Folder Structure

Setting up app.js

  • Import express module.

  • Import mongoose module

  • Import and configure dotenv module

  • Import CORS module

  • Use express() to start our app.

//app.js
const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose");

require("dotenv").config();

const app = express();
Enter fullscreen mode Exit fullscreen mode

On that app instance, we can now use all of the different methods. Let's start with some basic setup. Don't forget to configure the port as well.

// app.js
const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose");

require("dotenv").config();

const app = express();
const port = process.env.PORT || 5000;

app.use(cors());
app.use(express.json());
Enter fullscreen mode Exit fullscreen mode

Setting up MongoDB cloud cluster

MongoDB is a document-oriented database that is open source and cross-platform. MongoDB is a NoSQL database that stores data in JSON-like documents with optional schemas. Versions prior to October 16, 2018 are released under the AGPL license. All versions released after October 16, 2018, including bug fixes for previous versions, are distributed under the SSPL license v1.

Official MongoDB website
MongoDB cloud

Sign in to MongoDB
SignIn MongoDB

Create a Project
Create a Project

Adding members
Adding members

Creating a database
Creating a database

Creating a cluster
Creating a cluster

Selecting a cloud service provider
Choosing a cloud service provider

Security Quickstart
Security Quickstart

Finish and close to make a cluster and wait for the cluster to be built before proceeding (usually takes around 5 -10 minutes)
Database Deployment

Navigate to the network access tab and select "Add IP address."
IP address

Now, select the Choose a connection method.
Connection method

Connect your application by clicking on it and finally select the correct driver and version.
Connecting application

In the database, create a user. You'll need the username and password for the MongoDB URI and finally, create a database user.
Database access

Cloud atlas up and running
Cloud atlas up and running

Now, inside app.js create a new variable and name it DATABASE_CONNECTION. Inside it, create a string and simply paste the copied mongo DB connection URL or simply paste the link for the environment variables. Now, inside the link of Mongo Sb cloud atlas URL , enter your username and password, making sure to remove all the brackets and enter your own credentials. The second thing we need is a PORT, so simply enter the port number, for now, 6000, and finally, we will use mongoose to connect to our database, so enter mongoose. connect() which is a function with two different parameters. The first will be the DATABASE_CONNECTION, and the second will be an object with two different options. The first is useNewUrlParser, which we will set to true, and the second is useUnifiedTopology, which we will also set to true. These objects are not required, but we will see some errors or warnings on our console. Following that, let's chain a.then() and.catch() because this will return a promise, so inside .then() will call the app and invoke listen, which has two parameters, the first of which is PORT and the second of which is the callback function that will be executed if our application is successfully connected and finally, if the connection to the database is not successful we will simply console log our error message.

// app.js
const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose");

require("dotenv").config();

const app = express();

// app config
app.use(cors());
app.use(express.json());

// port and DB config
const DATABASE_CONNECTION = process.env.DATABASE_URL;
const PORT = process.env.PORT || 5000;

// mongoose connection
mongoose
  .connect(DATABASE_CONNECTION, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })
  .then(() =>
    app.listen(PORT, () =>
      console.log(`Server is running at : http://localhost:${PORT}`)
    )
  )
  .catch((error) => console.error(error));
Enter fullscreen mode Exit fullscreen mode

Insert mongodb+srv into the .env file.

PORT=6000
DATABASE_URL=mongodb+srv://pramit:<password>@cluster0.uauqv.mongodb.net/myFirstDatabase?retryWrites=true&w=majority
Enter fullscreen mode Exit fullscreen mode

That's all there is to it; we've successfully connected our server to the database.
Now that we've successfully connected to our database, let's get started on building our backend application's routes. To do so, we'll need to create a new folder called routes on the server directory. We will create a file called calorie.routes.js within the routes folder.

This is how your folders should be organized.

Folder Structure

Let's get started by importing the calorie and user routes into your app.js file . We can now connect calorie and user to our application using express middleware. Finally, your app.js file should like the following.

//app.js
const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose");

require("dotenv").config();

const app = express();

// app config
app.use(cors());
app.use(express.json());

// port and DB config
const DATABASE_CONNECTION = process.env.DATABASE_URL;
const PORT = process.env.PORT || 5000;

// mongoose connection
mongoose
    .connect(DATABASE_CONNECTION, {
        useNewUrlParser: true,
        useUnifiedTopology: true,
    })
    .then(() =>
        app.listen(PORT, () =>
            console.log(`Server is running at : http://localhost:${PORT}`)
        )
    )
    .catch((error) => console.error(error));

// routers
const calorie = require("./routes/calorie.routes.js");
const users = require("./routes/users.routes.js");

app.use("/calorie", calorie);
app.use("/users", users);
Enter fullscreen mode Exit fullscreen mode

We are going to add all of the routes as well as its controllers inside of calorie.routes.js and user.routes.js , so first we must import express from "express" and also configure our router. But first, let's make a model for our users and calorie.So, create a folder named models, and inside that folder, create two files called calorie.model.js and users.model.js, and paste the following code into each of them.
Now, your folder structure should look something like this

Folder Structure

//models/calorie.model.js
const mongoose = require("mongoose");

const Schema = mongoose.Schema;

const calorieSchema = new Schema({
    username: {
        type: String,
        required: true
    },
    description: {
        type: String,
        required: true
    },
    calories: {
        type: Number,
        required: true
    },
    date: {
        type: Date,
        required: true
    },
}, {
    timestamps: true,
});

const Calorie = mongoose.model("CalorieJournal", calorieSchema);

module.exports = Calorie;
Enter fullscreen mode Exit fullscreen mode

and

//models/users.model.js
const mongoose = require("mongoose");

const Schema = mongoose.Schema;

const userSchema = new Schema({
    username: {
        type: String,
        required: true,
        unique: true,
        trim: true,
        minlength: 2,
    },
}, {
    timestamps: true,
});

const User = mongoose.model("User", userSchema);

module.exports = User;
Enter fullscreen mode Exit fullscreen mode

Now we can begin adding our routes to it.

//routes/calorie.routes.js
const router = require("express").Router();
let Calorie = require("../models/calorie.model.js");

router.route("/").get((req, res) => {
    Calorie.find()
        .then((meals) => res.json(meals))
        .catch((err) => res.status(400).json("Error: " + err));
});

router.route("/add").post((req, res) => {
    const username = req.body.username;
    const description = req.body.description;
    const calories = Number(req.body.calories);
    const date = Date.parse(req.body.date);

    const addCalorie = new Calorie({
        username,
        description,
        calories,
        date,
    });

    addCalorie
        .save()
        .then(() => res.json("Calories Added Successfully"))
        .catch((err) => res.status(400).json("Error: " + err));
});
Enter fullscreen mode Exit fullscreen mode

Fetching all the calorie information.
Fetching all the calorie information.

Deleting single calorie information.
Deleting single calorie information

Updating single calorie information.
Updating single calorie information

Finally, export the router
export the router

Your calorie.route.js file should look like this.

//models/calorie.model.js
const router = require("express").Router();
let Calorie = require("../models/calorie.model.js");

router.route("/").get((req, res) => {
    Calorie.find()
        .then((meals) => res.json(meals))
        .catch((err) => res.status(400).json("Error: " + err));
});

router.route("/add").post((req, res) => {
    const username = req.body.username;
    const description = req.body.description;
    const calories = Number(req.body.calories);
    const date = Date.parse(req.body.date);

    const addCalorie = new Calorie({
        username,
        description,
        calories,
        date,
    });

    addCalorie
        .save()
        .then(() => res.json("Calories Added Successfully"))
        .catch((err) => res.status(400).json("Error: " + err));
});

router.route("/:id").get((req, res) => {
    Calorie.findById(req.params.id)
        .then((calories) => res.json(calories))
        .catch((err) => res.status(400).json("Error: " + err));
});

router.route("/:id").delete((req, res) => {
    Calorie.findByIdAndDelete(req.params.id)
        .then(() => res.json("Calories is deleted Successfully"))
        .catch((err) => res.status(400).json("Error: " + err));
});

router.route("/update/:id").post((req, res) => {
    Calorie.findById(req.params.id)
        .then((calories) => {
            calories.username = req.body.username;
            calories.description = req.body.description;
            calories.calories = Number(req.body.calories);
            calories.date = Date.parse(req.body.date);
            calories
                .save()
                .then(() => res.json("Calorie Updated Successfully"))
                .catch((err) => res.status(400).json("Err: " + err));
        })
        .catch((err) => res.status(400).json("Err: " + err));
});

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

Now let’s update user routes.

//routes/user.routes.js
const router = require("express").Router();
let User = require("../models/users.model.js");
Enter fullscreen mode Exit fullscreen mode

Fetch the user info
Fetching user info

Adding the user info
Adding the user info

Finally, export the router
exporting router

Your users.route.js file should look like this.

//routes/user.routes.js
const router = require("express").Router();
let User = require("../models/users.model.js");

// get user
router.route("/").get((req, res) => {
    User.find()
        .then((users) => res.json(users))
        .catch((err) => res.status(400).json("Error: " + err));
});

// add user
router.route("/add").post((req, res) => {
    const username = req.body.username;

    const newUser = new User({
        username
    });

    newUser
        .save()
        .then(() => res.json("User added Successfully"))
        .catch((err) => res.status(400).json("Error: " + err));
});

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

You should see something like this after restarting the server:

Server Running


Configuring our Frontend

We'll begin by using create-react-app to set up our frontend. We'll build the user interface and its features from the ground up. Let's go to work on our application right away.

Setting up react application bootstarpped using CRA

Let's start with the frontend and build it with react. The first thing you need do is install Node.js if it isn't already installed on your PC. So, head over to the official Node.js website and download the latest version. Node js is required in order to use the node package manager, generally known as NPM. Now open the client folder in your preferred code editor. I'll be using VScode . Next, open the integrated terminal and type npx create-react-app . This command will create a client application in the current directory, using the name client.

create react app

It normally only takes a few minutes to set up. Normally, we would use npm to get packages into a project, but in this case, we'll use npx, the package runner, which will download and configure everything for us so that we can get started with an excellent template right away. It's time to start our development server, so run npm start and the browser will open react-app instantly.

Starting react app

React boilerplate files cleanup

We must first tidy up our projects by eliminating some of the files provided by create-react-app before we can begin creating them. After you've cleaned up your files and folder , they should look like this.

Folder Structure

Adding and Installing some packages

We will need to install a few third-party packages for this project. so copy and paste the following command into your terminal

npm install bootstrap react-chartjs-2 chart.js axios react-datepicker react-router-dom
Enter fullscreen mode Exit fullscreen mode

After installing all these packages your packge.json file of client should look like this:

Package json

Let's construct seven separate folders / component inside the components folder after we've installed all of our project's dependencies and name it as Navbar, CalorieChart, UserChart, AddFood , AddUser , EditFood and DisplayFoodList .

Your file and folder structure should look something like this once you've added all of your components.

Folder structure

Now go to your app.js file and import the routers from react-router-dom and styles, as well as the bootstrap css file, also all the components as well and make the necessary changes to the code as follows.

App component

// app.js
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";

import Navbar from "./components/Navbar";
import DisplayFoodList from "./components/DisplayFoodList";
import EditFood from "./components/EditFood";
import AddFood from "./components/AddFood";
import AddUser from "./components/AddUser";

function App() {
  return (
    <>
      <Router>
        <Navbar />
        <br />
        <Routes>
          <Route path="/" exact element={<DisplayFoodList />} />
          <Route path="/edit/:id" element={<EditFood />} />
          <Route path="/create" element={<AddFood />} />
          <Route path="/user" element={<AddUser />} />
        </Routes>
      </Router>
    </>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

then go to the navbar component and paste the code below into it.

Navbar component

//components/Navbar/Navbar.js
import React from "react";
import { Link } from "react-router-dom";

const Navbar = () => {
  return (
    <nav
      className="navbar navbar-expand-lg navbar-light static-top mb-0 shadow"
      style={{ backgroundColor: "#8661d1" }}
    >
      <div className="container">
        <Link to="/">
          <img
            alt="Calorie Journal Logo"
            src="https://user-images.githubusercontent.com/37651620/142762093-45207811-0c6e-4b62-9cb2-8d0009efb4ea.png"
            width="70"
            height="70"
            className="d-inline-block align-top"
          />
        </Link>
        <Link
          className="navbar-brand"
          to="/"
          className="navbar-brand"
          style={{
            color: "white",
            fontSize: "1.5rem",
            marginRight: "15rem",
            marginLeft: "30rem",
          }}
        >
          <img
            src="https://user-images.githubusercontent.com/37651620/142764762-fef8f764-4cd5-44c6-8b9a-cffcfab2ccf8.png"
            alt="calorie journal"
            style={{ height: "100px" }}
          />
        </Link>

        <div className="collapse navbar-collapse">
          <ul className="navbar-nav ml-auto">
            <li className="nav-item">
              <Link
                className="nav-link"
                to="/"
                className="nav-link"
                style={{
                  fontSize: "0.2rem",
                  color: "white",
                }}
              >
                <button type="button" className="btn btn-info">
                  Calorie Info
                </button>
              </Link>
            </li>
            <li className="nav-item active">
              <Link
                className="nav-link"
                to="/create"
                className="nav-link"
                style={{
                  fontSize: "0.2rem",
                  color: "white",
                }}
              >
                <button type="button" className="btn btn-info">
                  βž• Add food
                </button>
              </Link>
            </li>
            <li className="nav-item">
              <Link
                className="nav-link"
                to="/user"
                className="nav-link"
                style={{
                  fontSize: "0.2rem",
                  color: "white",
                }}
              >
                <button type="button" className="btn btn-warning">
                  βž• Add User
                </button>
              </Link>
            </li>
          </ul>
        </div>
      </div>
    </nav>
  );
};

export default Navbar;
Enter fullscreen mode Exit fullscreen mode

It's time to define our AddFood component now that we've successfully introduced the navbar component to our application.

import React,{useState,useEffect,useRef} from "react";
import axios from "axios";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
Enter fullscreen mode Exit fullscreen mode

In AddFood component, add a useState() hook, which will allow us to incorporate the state into our functional component. useState() does not operate with object values, unlike state in class components. We can use primitives directly to build multiple react hooks for multiple variables if necessary.

const [state, setState] = useState(initialState);

useState

Hooks must always be declared at the beginning of a function in React. This also aids in the component's state maintenance as well as preservation between renderings.

state

useRef

what is useRef() hook ?
This hook simply returns a mutable ref object with the passed argument as its.current property (initialValue). The returned object will be retained for the duration of the component's lifetime.

const refContainer = useRef(initialValue);
Enter fullscreen mode Exit fullscreen mode

Let us jump right back into the code and implement useRef functionality

const userInputRef = useRef("userInput");
Enter fullscreen mode Exit fullscreen mode

Let's have a look at the useEffect() hook. You notify React that your component needs to perform something after it renders by using this Hook. After completing the DOM modifications, React will remember the function you gave (which we'll refer to as our "effect"). We set the document title to achieve this, but we could alternatively perform data fetching or call another imperative API. Using useEffect() within the component allows us to directly access the count state variable (or any props) from the effect. It's already in the function scope, so we don't need a new API to read it. Hooks make use of JavaScript closures rather than providing React-specific APIs where JavaScript already provides it. useEffect() The hook is comparable to the life-cycle methods for class components that we are familiar with. It executes after each component render, including the initial render. As a result, componentDidMount, componentDidUpdate, and componentWillUnmount can all be thought of as a single component. We can pass dependencies to the effect to determine the behavior of when the effect should execute (just on initial render, or only when a specific state variable changes). This hook also has a clean-up option, which allows resources to be cleaned up before the component is destroyed. useEffect(didUpdate) is the effect's fundamental syntax.

useEffect

Let's make a function that fetches all the user information

useEffect(() => {
  axios
    .get("http://localhost:5000/users/")
    .then((response) => {
      if (response.data.length > 0) {
        setUsers(response.data.map((user) => user.username));
        setUsername(response.data[0].username);
      }
    })
    .catch((error) => {
      console.log(error);
    });
}, []);
Enter fullscreen mode Exit fullscreen mode

Now, create five function or handlers and name it as handleUsername, handlDescription, handleCalories, handleDate and handleSubmit

function handleUsername(e) {
  setUsername(e.target.value);
}

function handleDescription(e) {
  setDescription(e.target.value);
}

function handleCalories(e) {
  setCalories(e.target.value);
}

function handleDate(date) {
  setDate(date);
}

function handleSubmit(e) {
  e.preventDefault();

  const meal = {
    username,
    description,
    calories,
    date,
  };

  console.log(meal);

  axios
    .post("http://localhost:5000/calorie/add", meal)
    .then((res) => console.log(res.data));

  window.location = "/";
}
Enter fullscreen mode Exit fullscreen mode

Finally, your AddFood component should look something like this

AddFood component

//components/AddFood
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";

const AddFood = () => {
  const [username, setUsername] = useState("");
  const [description, setDescription] = useState("");
  const [calories, setCalories] = useState("");
  const [date, setDate] = useState(new Date());
  const [users, setUsers] = useState([]);
  const userInputRef = useRef("userInput");

  useEffect(() => {
    axios
      .get("http://localhost:5000/users/")
      .then((response) => {
        if (response.data.length > 0) {
          setUsers(response.data.map((user) => user.username));
          setUsername(response.data[0].username);
        }
      })
      .catch((error) => {
        console.log(error);
      });
  }, []);

  function handleUsername(e) {
    setUsername(e.target.value);
  }

  function handleDescription(e) {
    setDescription(e.target.value);
  }

  function handleCalories(e) {
    setCalories(e.target.value);
  }

  function handleDate(date) {
    setDate(date);
  }

  function handleSubmit(e) {
    e.preventDefault();

    const meal = {
      username,
      description,
      calories,
      date,
    };

    console.log(meal);

    axios
      .post("http://localhost:5000/calorie/add", meal)
      .then((res) => console.log(res.data));

    window.location = "/";
  }
  return (
    <>
      <div className="container">
        <div className="card border-0 shadow my-4">
          <div className="card-body p-3"></div>
          <div>
            <h3 style={{ textAlign: "center" }}>
              <img
                src="https://user-images.githubusercontent.com/37651620/142764215-78f5b75f-4871-451e-9a4d-dd77cc667fc5.png"
                alt="Food"
                style={{ height: "150px" }}
              />{" "}
            </h3>
            <form onSubmit={handleSubmit}>
              <div
                className="form-group"
                style={{
                  marginLeft: "20px",
                  marginBottom: "15px",
                  marginRight: "20px",
                }}
              >
                <label>πŸ‘€ User name: </label>
                <select
                  ref={userInputRef}
                  required
                  className="form-control"
                  value={username}
                  onChange={handleUsername}
                >
                  {users.map(function (user) {
                    return (
                      <option key={user} value={user}>
                        {user}
                      </option>
                    );
                  })}
                </select>
              </div>
              <div
                className="form-group"
                style={{
                  marginLeft: "20px",
                  marginBottom: "25px",
                  marginRight: "20px",
                }}
              >
                <label>πŸ₯‘ Food Info: </label>
                <input
                  type="text"
                  required
                  className="form-control"
                  value={description}
                  onChange={handleDescription}
                />
              </div>
              <div
                className="form-group"
                style={{
                  marginLeft: "20px",
                  marginBottom: "15px",
                  marginRight: "20px",
                }}
              >
                <label>πŸ”₯ Calories: </label>
                <input
                  type="text"
                  className="form-control"
                  value={calories}
                  onChange={handleCalories}
                />
              </div>
              <div
                className="form-group"
                style={{
                  marginLeft: "20px",
                  marginBottom: "15px",
                  marginRight: "20px",
                }}
              >
                <div style={{ textAlign: "center", cursor: "pointer" }}>
                  <label>Date: </label>
                  <div>
                    <DatePicker selected={date} onChange={handleDate} />
                  </div>
                </div>
              </div>

              <div className="form-group" style={{ textAlign: "center" }}>
                <input
                  type="submit"
                  value="Add Meal"
                  className="btn"
                  style={{
                    color: "white",
                    backgroundColor: "#8661d1",
                    marginBottom: "25px",
                  }}
                />
              </div>
            </form>
          </div>
        </div>
      </div>
    </>
  );
};

export default AddFood;
Enter fullscreen mode Exit fullscreen mode

Now, It's time to define our AddUser component now that we've successfully introduced the AddFood component to our application. Copy the following code and paste it inside the AddUser component.

AddUser component

//components/AddUser
import React, { useState } from "react";
import axios from "axios";

const AddUser = () => {
  const [username, setUsername] = useState("");

  function handleUsername(e) {
    setUsername(e.target.value);
  }

  function handleSubmit(e) {
    e.preventDefault();
    const user = {
      username,
    };
    console.log(user);
    axios
      .post("http://localhost:5000/users/add", user)
      .then((res) => console.log(res.data));
    setUsername("");
  }

  return (
    <>
      <div class="container">
        <div class="card border-0 shadow my-4">
          <div class="card-body p-3"></div>
          <div>
            <h3 style={{ textAlign: "center", marginBottom: "15px" }}>
              <img
                src="https://user-images.githubusercontent.com/37651620/142767072-ff777861-7ee9-4355-b48e-a624e8de085b.png"
                alt="Logo"
                style={{ height: "150px" }}
              />
            </h3>
            <form onSubmit={handleSubmit}>
              <div
                className="form-group"
                style={{
                  marginLeft: "20px",
                  marginBottom: "15px",
                  marginRight: "20px",
                }}
              >
                <label>πŸ‘€ User name:</label>
                <input
                  type="text"
                  required
                  className="form-control"
                  value={username}
                  onChange={handleUsername}
                />
              </div>
              <div
                className="form-group"
                style={{
                  textAlign: "center",
                }}
              >
                <input
                  type="submit"
                  value="Create User"
                  className="btn "
                  style={{
                    color: "white",
                    marginBottom: "25px",
                    backgroundColor: "#8661d1",
                  }}
                />
              </div>
            </form>
          </div>
        </div>
      </div>
    </>
  );
};

export default AddUser;
Enter fullscreen mode Exit fullscreen mode

Now that we've completed the AddUser component, it's time to construct a feature that allows us to change our data, therefore we'll make an EditFood component.

EditFood component

//components/EditFood
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";

const EditFood = (props) => {
  const [username, setUsername] = useState("");
  const [description, setDescription] = useState("");
  const [calories, setCalories] = useState("");
  const [date, setDate] = useState(new Date());
  const [users, setUsers] = useState([]);
  const userInputRef = useRef("userInput");

  useEffect(() => {
    axios
      .get("http://localhost:5000/calorie/" + props.match.params.id)
      .then((response) => {
        setUsername(response.data.username);
        setDescription(response.data.description);
        setCalories(response.data.calories);
        setDate(new Date(response.data.date));
      })
      .catch((error) => {
        console.log(error);
      });

    axios
      .get("http://localhost:5000/users/")
      .then((response) => {
        if (response.data.length > 0) {
          setUsers(response.data.map((user) => user.username));
          setUsername(response.data[0].username);
        }
      })
      .catch((error) => {
        console.log(error);
      });
  }, [props.match.params.id]);

  function handleUsername(e) {
    setUsername(e.target.value);
  }

  function handleDescription(e) {
    setDescription(e.target.value);
  }

  function handleCalories(e) {
    setCalories(e.target.value);
  }

  function handleDate(date) {
    setDate(date);
  }

  function handleSubmit(e) {
    e.preventDefault();

    const food = {
      username,
      description,
      calories,
      date,
    };

    console.log(food);

    axios
      .post("http://localhost:5000/calorie/update", food)
      .then((res) => console.log(res.data));

    window.location = "/";
  }
  return (
    <>
      <div className="container">
        <div className="card border-0 shadow my-4">
          <div className="card-body p-3"></div>
          <div>
            <h3 style={{ textAlign: "center" }}>
              <img
                src="https://user-images.githubusercontent.com/37651620/142764215-78f5b75f-4871-451e-9a4d-dd77cc667fc5.png"
                alt="Food"
                style={{ height: "150px" }}
              />{" "}
            </h3>
            <form onSubmit={handleSubmit}>
              <div
                className="form-group"
                style={{
                  marginLeft: "20px",
                  marginBottom: "15px",
                  marginRight: "20px",
                }}
              >
                <label>πŸ‘€ User name: </label>
                <select
                  ref={userInputRef}
                  required
                  className="form-control"
                  value={username}
                  onChange={handleUsername}
                >
                  {users.map(function (user) {
                    return (
                      <option key={user} value={user}>
                        {user}
                      </option>
                    );
                  })}
                </select>
              </div>
              <div
                className="form-group"
                style={{
                  marginLeft: "20px",
                  marginBottom: "25px",
                  marginRight: "20px",
                }}
              >
                <label>πŸ₯‘ Food Info: </label>
                <input
                  type="text"
                  required
                  className="form-control"
                  value={description}
                  onChange={handleDescription}
                />
              </div>
              <div
                className="form-group"
                style={{
                  marginLeft: "20px",
                  marginBottom: "15px",
                  marginRight: "20px",
                }}
              >
                <label>πŸ”₯ Calories: </label>
                <input
                  type="text"
                  className="form-control"
                  value={calories}
                  onChange={handleCalories}
                />
              </div>
              <div
                className="form-group"
                style={{
                  marginLeft: "20px",
                  marginBottom: "15px",
                  marginRight: "20px",
                }}
              >
                <div style={{ textAlign: "center", cursor: "pointer" }}>
                  <label>Date: </label>
                  <div>
                    <DatePicker selected={date} onChange={handleDate} />
                  </div>
                </div>
              </div>

              <div className="form-group" style={{ textAlign: "center" }}>
                <input
                  type="submit"
                  value="Add Meal"
                  className="btn"
                  style={{
                    color: "white",
                    backgroundColor: "#8661d1",
                    marginBottom: "25px",
                  }}
                />
              </div>
            </form>
          </div>
        </div>
      </div>
    </>
  );
};

export default EditFood;
Enter fullscreen mode Exit fullscreen mode

Let's concentrate on visualizing the fetched data into charts using the react-chartjs-2 library before we start fetching and showing the whole information on our home page.
So let's make two distinct components, one for a bar graph and the other for a pie chart, and once you've done that, copy the following code into each component.

UserChart

//components/UserChart.js
import React, { useEffect, useState } from "react";
import { Pie } from "react-chartjs-2";
import axios from "axios";

const Delayed = ({ children, waitBeforeShow = 4500 }) => {
  const [isShown, setIsShown] = useState(false);

  useEffect(() => {
    setTimeout(() => {
      setIsShown(true);
    }, waitBeforeShow);
  }, [waitBeforeShow]);

  return isShown ? children : null;
};

const UserChart = () => {
  const [chartData, setChartData] = useState({});

  async function getData() {
    let username = [];
    let calories = [];
    await axios
      .get("http://localhost:5000/calorie/")
      .then((res) => {
        console.log(res);
        for (const dataObj of res.data) {
          username.push(dataObj.username);
          calories.push(parseInt(dataObj.calories));
          console.log(username, calories);
        }
        setChartData({
          labels: username,
          datasets: [
            {
              label: "Calories",
              data: calories,
              backgroundColor: [
                "#f42f42",
                "#5ab950",
                "#fe812a",
                "#ffc748",
                "#6b71c7",
                "#8661d1",
                "#8a2cba",
              ],
              borderColor: [
                "#f42f42",
                "#5ab950",
                "#fe812a",
                "#ffc748",
                "#6b71c7",
                "#8661d1",
                "#8a2cba",
              ],
              borderWidth: 2,
            },
          ],
        });
      })
      .catch((err) => {
        console.log(err);
      });
    console.log(username, calories);
  }

  useEffect(() => {
    getData();
  }, []);

  return (
    <div className="App">
      <div>
        <h5
          style={{
            fontSize: "20",
            textAlign: "center",
            marginTop: "1em",
            marginBottom: "1em",
          }}
        >
          Calorie per user
        </h5>
        <Delayed>
          <Pie
            data={chartData}
            options={{
              title: "{"
                text: "Calorie per User",
                fontSize: 10,
                fontColor: "#212529",
              },
              maintainAspectRatio: true,
            }}
          />
        </Delayed>
      </div>
    </div>
  );
};

export default UserChart;
Enter fullscreen mode Exit fullscreen mode

CalorieChart component

//components/CalorieChart
import React, { useEffect, useState } from "react";
import { Bar } from "react-chartjs-2";
import axios from "axios";

const Delayed = ({ children, waitBeforeShow = 4500 }) => {
  const [isShown, setIsShown] = useState(false);

  useEffect(() => {
    setTimeout(() => {
      setIsShown(true);
    }, waitBeforeShow);
  }, [waitBeforeShow]);

  return isShown ? children : null;
};

const CalorieChart = () => {
  const [chartData, setChartData] = useState({});

  async function getData() {
    let foodCal = [];
    let caloriesCal = [];
    await axios
      .get("http://localhost:5000/calorie/")
      .then((res) => {
        console.log(res);
        for (let dataObj of res.data) {
          foodCal.push(dataObj.description);
          caloriesCal.push(parseInt(dataObj.caloriesCal));
          console.log("foodCal, caloriesCal", foodCal, caloriesCal);
        }
        setChartData({
          labels: foodCal,
          datasets: [
            {
              label: "Cal",
              data: caloriesCal,
              backgroundColor: [
                "#f42f42",
                "#5ab950",
                "#fe812a",
                "#ffc748",
                "#6b71c7",
                "#8661d1",
                "#8a2cba",
              ],
            },
          ],
        });
      })
      .catch((err) => {
        console.log(err);
      });
  }

  useEffect(() => {
    getData();
  }, []);

  return (
    <div className="App">
      <h4>Food Analytics</h4>

      <h5
        style={{
          fontSize: "20",
          textAlign: "center",

          marginBottom: "1em",
        }}
      >
        Calorie Intake per each Food
      </h5>
      <div>
        <Delayed>
          <Bar
            data={chartData}
            options={{
              responsive: true,
              title: "{"
                text: "Calorie Per Food ",
                fontSize: 20,
                fontColor: "#212529",
              },
              scales: {
                yAxes: [
                  {
                    ticks: {
                      autoSkip: true,
                      maxTicksLimit: 10,
                      beginAtZero: true,
                    },
                    gridLines: {
                      // display: true,
                    },
                  },
                ],
                xAxes: [
                  {
                    gridLines: {
                      display: true,
                    },
                  },
                ],
              },
            }}
          />
        </Delayed>
      </div>
    </div>
  );
};

export default CalorieChart;
Enter fullscreen mode Exit fullscreen mode

Finally, let's work on the DisplayFoodList component, so first import the link from react-router, then import the axios package, then import the two previously created chart components, then create a FoodTrack component inside the DisplayFoodList file and add the following code, and finally create three functions named DisplayFoodList, deleteMeal, and malList, and finally use all the imported data inside the return statement and don’t forget to invoke the mailList function inside the tbody.Finally, if you followed all the steps correctly then your DisplayFoodList component should resemble the following.

DisplayFoodList component

//components/DisplayFoodList
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
import CalorieChart from "../CalorieChart";
import UserChart from "../UserChart";

const FoodTrack = (props) => (
  <tr>
    <td>
      <Link to={"/edit/" + props.meal._id} style={{ color: " #a04949" }}>
        <img
          src="https://user-images.githubusercontent.com/37651620/142769270-6128d45e-3650-4b66-bc0b-a76e3991fa1f.png"
          alt="edit"
          style={{ height: "40px" }}
        />
      </Link>{" "}
      |{" "}
      <a
        href="#"
        onClick={() => {
          props.deleteMeal(props.meal._id);
          window.location.reload(false);
        }}
        style={{ color: " #a04949" }}
      >
        <img
          src="https://user-images.githubusercontent.com/37651620/142769328-23d55107-8bed-4fa0-92b8-cca7df931083.png"
          alt="edit"
          style={{ height: "40px" }}
        />
      </a>
    </td>
    <td>{props.meal.username}</td>
    <td>{props.meal.description}</td>
    <td>{props.meal.calories}</td>
    <td>{props.meal.date.substring(0, 10)}</td>
  </tr>
);

const DisplayFoodList = () => {
  const [foods, setFoods] = useState([]);

  useEffect(() => {
    axios
      .get("http://localhost:5000/calorie/")
      .then((response) => {
        setFoods(response.data);
      })
      .catch((error) => {
        console.log(error);
      });
  }, []);

  function deleteMeal(id) {
    axios.delete("http://localhost:5000/calorie/" + id).then((response) => {
      console.log(response.data);
    });
    setFoods(foods.filter((el) => el._id !== id));
  }

  const mealList = () => {
    return foods.map((currentmeal) => {
      return (
        <FoodTrack
          meal={currentmeal}
          deleteMeal={deleteMeal}
          key={currentmeal._id}
        />
      );
    });
  };

  return (
    <>
      <>
        <div className="container">
          <div className="card border-0 shadow my-4">
            <div className="card-body p-5">
              <h3 style={{ textAlign: "center", marginBottom: "15px" }}>
                Calorie Journal
              </h3>
              <table className="table" style={{ textAlign: "center" }}>
                <thead className="thead" style={{ backgroundColor: "#8661d1" }}>
                  <tr>
                    <th>Edit/Delete</th>
                    <th>πŸ‘€ Username</th>
                    <th>πŸ“™ Description</th>
                    <th>πŸ”₯ Calories</th>
                    <th>πŸ“… Date</th>
                  </tr>
                </thead>
                <tbody>{mealList()}</tbody>
              </table>
            </div>
          </div>
        </div>
        <div className="container">
          <div
            className="card border-0 shadow my-2"
            style={{ padding: "2rem" }}
          >
            <div className="card-body p-1"></div>
            <UserChart />
            <CalorieChart />
          </div>
        </div>
      </>
    </>
  );
};

export default DisplayFoodList;
Enter fullscreen mode Exit fullscreen mode

We've covered a lot of ground to provide you with the information you'll need to create a full-fledged MERN stack application from the ground up.

You can find the whole source code here.

https://github.com/aviyeldevrel/devrel-tutorial-projects/tree/main/MERN-saas-project


Main article available here => https://aviyel.com/post/1323

Happy Coding!!

Follow @aviyelHQ or sign-up on Aviyel for early access if you are a project maintainer, contributor, or just an Open Source enthusiast.

Join Aviyel's Discord => Aviyel's world

Twitter =>[https://twitter.com/AviyelHq]

Discussion (3)

Collapse
fitrarhm profile image
fitrarhm

How much do you spend to made this great article + code + illustration?

Collapse
pramit_marattha profile image
Pramit Marattha Author

Let's say a couple of days. (3 - 4 days )

Collapse
drsimplegraffiti profile image
Abayomi Ogunnusi

Thanks for this... It's timely