DEV Community

Cover image for Crafting a stunning CRUD application with MERN stack  šŸ”„
Pramit Marattha for Aviyel Inc

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

Crafting a stunning CRUD application with MERN stack šŸ”„

In this blog tutorial, we will set up a full-stack app to perform CRUD operations using the MERN stack, MongoDB for the database, Express and Node for the backend, and React as the frontend. This blog tutorial should help you understand the basic MERN stack CRUD operations.

Here's a look at the final version of our application.

Demo

Setting up Frontend

We'll start by setting up our frontend first using create-react-app . So, without further ado, let's get started.

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

Client Directory

Server Directory

Folder Structure

We will be building the UI and its functionalities from absolute ground level. Now, lets start and craft our application.

Installing react application

Let us begin with the frontend part and craft it using react. So, if Node.js isn't already installed on your system, the first thing you should do is install it. So, go to the official Node.js website and install the correct and appropriate version. We need node js so that we can use the node package manager, also known as NPM.

Now, open client folder inside the code editor of your choice. For this tutorial, I will be using VScode. Next step, letā€™s open the integrated terminal and type npx create-react-app . this command will create the app inside the current directory and that application will be named as client

npx

It usually takes only a few minutes to install. Normally, we would use npm to download packages into the project, but in this case, we are using npx, the package runner, which will download and configure everything for us so that we can start with an amazing template. It's now time to start our development server, so simply type npm start, and the browser will automatically open react-app.

Starting react app

Now, within the client folder install the following dependencies.

npm i axios react-router-dom
Enter fullscreen mode Exit fullscreen mode

Dependencies

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

{
  "name": "client",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.14.1",
    "@testing-library/react": "^11.2.7",
    "@testing-library/user-event": "^12.8.3",
    "axios": "^0.24.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-router-dom": "^5.3.0",
    "react-scripts": "4.0.3",
    "web-vitals": "^1.1.2"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Project cleanup

Before we begin building our projects, we must first clean them up by removing some of the files provided by create-react-app. Your src files should look like this after you've cleaned them up

Folder structure

Now, within the src folder, make another folder called components, and within that folder, make three folder/components: DisplayTodo, CreateTodo, TodoLists and UpdateTodo.

Components

DisplayTodo
Display Todo

To begin, we will create the DisplayTodo component, which will read all of the documents created. The first step is, import the react useState and useEffect hooks and then import axios from the Axios package.Ā We will retrieve documents from the database and store them in the state todoData in the DisplayTodo function component. To retrieve the document, we will use axios to send a GET request to the backend. When we receive the data, we will use setTodoData to store it in todoData and log it. If we receive an error, we will also log it. Because we want the data to load when the page loads, we'll make the GET request from the useEffect hook.

// components/DisplayTodo.js

import { useState, useEffect } from "react";
import axios from "axios";
import { Link } from "react-router-dom";
import TodoLists from "../TodoLists";

const DisplayTodo = () => {
  const [infoTodo, setInfoTodo] = useState([]);

  useEffect(() => {
    axios
      .get("http://localhost:4000/api/todoapp")
      .then((res) => {
        console.log(res.data);
        setInfoTodo(res.data);
      })
      .catch((err) => {
        console.log(err);
      });
  }, []);

  const deleteHandler = (e) => {
    axios.delete(`http://localhost:4000/api/todoapp/${e.target.name}`);
    setInfoTodo((data) => {
      return data.filter((todo) => todo._id !== e.target.name);
    });
  };

  return (
    <section className="todo-container">
      <Link to="/add-todo" className="todo-btn-new">
        <button className="todo-btn">Add new todo</button>
      </Link>
      <section className="todo-data">
        <h1></h1>
        <ul className="todo-list-block">
          {infoTodo.map((data) => (
            <TodoCard data={data} deleteHandler={deleteHandler} />
          ))}
        </ul>
      </section>
    </section>
  );
};

export default DisplayTodo;
Enter fullscreen mode Exit fullscreen mode

TodoList

TodoList component

Then we'll make the TodoList component to display the todo's contents. We will iterate over todoData and pass the contents to TodoList , which will display the contents of each to-do document.

// components/TodoList.js

import React from "react";

const TodoList = ({ todoInfos, deleteHandler }) => {
  const { _id, tilte, description } = todoInfos;
  return (
    <li key={_id}>
      <div className="title-description">
        <h3>{title}</h3>
        <p>{description}</p>
      </div>

      <div className="button-container">
        <button name={_id} className="button">
          šŸ–Šļø
        </button>
        <button name={_id} className="button" onClick={deleteHandler}>
          šŸ—‘ļø
        </button>
      </div>
    </li>
  );
};
Enter fullscreen mode Exit fullscreen mode

CreateTodo

Create Todo

To create a new todo, we will use axios to send a POST request to our server. So letā€™s import the react useState hook and after that, import the Link from react-router-dom.

Now, create a function handler change that and you'll get the input data again create a new function handler. Finally, Submitting this will cause the POST request to be sent to the server. Declare data using the useState hook and the JSON below.

"description": "", "title": ""
Enter fullscreen mode Exit fullscreen mode

When the input changes, we will update the data in the handleChange method. We'll call setTodoInfo() and declare an arrow function inside that will copy the previous data's contents if any exist. In this case, e.target.name will be the name of the input element, which will either have a title or a description. In the submitHanlder method, To prevent the page from reloading when the submit button is clicked, use e.preventDefault() Send the data in the form of a POST request to the server. If the data was successfully transmitted to the server, the state data should be reset.

// components/CreateTodo.js

import { useState } from "react";
import axios from "axios";

const CreateTodo = () => {
  const [todoInfo, setTodoInfo] = useState({ title: "", description: "" });

  function handleChange(e) {
    setTodoInfo((data) => ({ ...data, [e.target.name]: e.target.value }));
  }

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

    axios
      .post("http://localhost:4000/api/todoapp", todoInfo)
      .then((res) => {
        setTodoInfo({ title: "", description: "" });
        console.log(res.data.message);
      })
      .catch((err) => {
        console.log("Error couldn't create TODO");
        console.log(err.message);
      });
  }

  return (
    <section className="container">
      <button type="button" className="todo-btn todo-btn-back">
        šŸ”™ back
      </button>

      <section className="todo-data">
        <form onSubmit={handleSubmit} className="form-container" noValidate>
          <label className="label" htmlFor="title">
            Todo Title
          </label>
          <input
            type="text"
            name="title"
            value={todoInfo.title}
            onChange={handleChange}
            className="input"
          />
          <label className="label" htmlFor="description">
            Describe it !
          </label>
          <input
            type="textarea"
            name="description"
            value={todoInfo.description}
            onChange={handleChange}
            className="input"
          />
          <button type="submit" className="todo-btn">
            āž• create todo
          </button>
        </form>
      </section>
    </section>
  );
};

export default CreateTodo;
Enter fullscreen mode Exit fullscreen mode

Now, letā€™s define a deleteHandler function inside DisplayTodo component that will send a DELETE request to the server. To delete a document from the database, this function will require the document's _id. It will also add the filtered array to the array todo. TodoList component accepts the deleteHandler method as a parameter. TodoList component should be updated to include the deleteHandler parameter. Add a onClick event for the delete button and pass the deleteHandler method as a parameter.
After making the aforementioned changes, the code will look something like this.

Display Todo component

//components/DisplayTodo.js

import { useState, useEffect } from "react";
import axios from "axios";
import TodoLists from "../TodoLists";

const DisplayTodo = () => {
  const [infoTodo, setInfoTodo] = useState([]);
  const [id, setId] = useState("");
  const [update, setUpdate] = useState(false);
  const [infoTodo, setInfoTodo] = useState([]);
  const [modal, setModal] = useState(false);

  useEffect(() => {
    axios
      .get("http://localhost:8000/api/todo")
      .then((res) => {
        console.log(res.data);
        setInfoTodo(res.data);
      })
      .catch((err) => {
        console.log(err);
      });
  }, []);

  const updateHandler = () => {
    setUpdate(!update);
  };

  const closeHandler = () => {
    setId("");
    setModal(false);
  };

  const deleteHandler = (e) => {
    axios.delete(`http://localhost:8000/api/todo/${e.target.name}`);
    setInfoTodo((data) => {
      return data.filter((todo) => todo._id !== e.target.name);
    });
  };

  return (
    <section className="container">
      <button className="todo-btn">āž• Add new todo</button>
      <section className="todo-data">
        <h1></h1>
        <ul className="todo-list-block">
          {infoTodo.map((todoInfo, index) => (
            <TodoLists
              key={index}
              todoInfos={todoInfo}
              deleteHandler={deleteHandler}
            />
          ))}
        </ul>
      </section>
      {modal ? (
        <section className="update-container">
          <div className="update-todo-data">
            <p onClick={closeHandler} className="close">
              &times;
            </p>
          </div>
        </section>
      ) : (
        ""
      )}
    </section>
  );
};

export default DisplayTodo;
Enter fullscreen mode Exit fullscreen mode

TodoList component should look something like this:

TodoList

// components/TodoList.js

import React from "react";

const TodoLists = ({ todoInfos }) => {
  const { _id, title, description } = todoInfos;

  return (
    <li key={_id}>
      <div className="title-description">
        <h2>{title}</h2>
        <h1></h1>
        <p>{description}</p>
      </div>
      <h1></h1>
      <div className="todo-btn-container">
        <button className="todo-btn" name={_id}>
          šŸ–Šļø
        </button>
        <button className="todo-btn" name={_id}>
          šŸ—‘ļø
        </button>
      </div>
    </li>
  );
};

export default TodoLists;\
Enter fullscreen mode Exit fullscreen mode

We must first update the App.js file before we can use the CreateTodo component. BrowserRouter and Route should be imported from react-router-dom. Import the CreateTodo component from the components/createTodo directory. Create a Route for the home page and pass the ShowTodoList component through it and make a Route for adding a new todo /add-list and wrap the Routes within the BrowserRouter.

After you've made the changes, the App.js file should look like this.

App component

// App.js

import { BrowserRouter, Route } from "react-router-dom";
import DisplayTodo from "./components/DisplayTodo";
import CreateTodo from "./components/CreateTodo";
import "./App.css";

function App() {
  return (
    <div className="todo-Container">
      <BrowserRouter>
        <Route exact path="/" component={DisplayTodo} />
        <Route path="/add-list" component={CreateTodo} />
      </BrowserRouter>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Now, import the Link from react-router-dom. and wrap a button within a Link tag. After you've made the changes, the DisplayTodo should look like this.

Display Todo Component

// components/DisplayTodo.js

import { useState, useEffect } from "react";
import axios from "axios";
import { Link } from "react-router-dom";
import TodoLists from "../TodoLists";

export function DisplayTodo() {
  const [id, setId] = useState("");
  const [update, setUpdate] = useState(false);
  const [infoTodo, setInfoTodo] = useState([]);
  const [modal, setModal] = useState(false);

  useEffect(
    function () {
      axios
        .get("http://localhost:4000/api/todoapp")
        .then((res) => {
          setInfoTodo(res.data);
        })
        .catch((err) => {
          console.log(err.message);
        });
    },
    [update]
  );

  const editHandler = (e) => {
    setId(e.target.name);
    setModal(true);
  };

  const updateHandler = () => {
    setUpdate(!update);
  };

  const deleteHandler = (e) => {
    axios.delete(`http://localhost:4000/api/todoapp/${e.target.name}`);

    setInfoTodo((data) => {
      return data.filter((todo) => todo._id !== e.target.name);
    });
  };

  const closeHandler = () => {
    setId("");
    setModal(false);
  };

  return (
    <section className="container">
      <Link to="/add-list" className="button-new">
        <button className="todo-btn">āž• Add new todo</button>
      </Link>
      <section className="todo-data">
        <h1></h1>
        <ul className="todo-list-block">
          {infoTodo.map((todoInfo, index) => (
            <TodoLists
              key={index}
              todoInfos={todoInfo}
              editHandler={editHandler}
              deleteHandler={deleteHandler}
            />
          ))}
        </ul>
      </section>
      {modal ? (
        <section className="update-container">
          <div className="update-todo-data">
            <p onClick={closeHandler} className="close">
              &times;
            </p>
          </div>
        </section>
      ) : (
        ""
      )}
    </section>
  );
}

export default DisplayTodo;
Enter fullscreen mode Exit fullscreen mode

Now again, import the Link from react-router-dom and wrap a button within a Link tag. After you've made the changes, the CreateTodo should look like this.

Create Todo Component

// components/CreateTodo.js

import { useState } from "react";
import { Link } from "react-router-dom";
import axios from "axios";

const CreateTodo = () => {
  const [todoInfo, setTodoInfo] = useState({ title: "", description: "" });

  function handleChange(e) {
    setTodoInfo((data) => ({ ...data, [e.target.name]: e.target.value }));
  }

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

    axios
      .post("http://localhost:4000/api/todoapp", todoInfo)
      .then((res) => {
        setTodoInfo({ title: "", description: "" });
        console.log(res.data.message);
      })
      .catch((err) => {
        console.log("Error couldn't create TODO");
        console.log(err.message);
      });
  }

  return (
    <section className="container">
      <Link to="/">
        <button type="button" className="todo-btn todo-btn-back">
          šŸ”™ back
        </button>
      </Link>

      <section className="todo-data">
        <form onSubmit={handleSubmit} className="form-container" noValidate>
          <label className="label" htmlFor="title">
            Todo Title
          </label>
          <input
            type="text"
            name="title"
            value={todoInfo.title}
            onChange={handleChange}
            className="input"
          />
          <label className="label" htmlFor="description">
            Describe it !
          </label>
          <input
            type="textarea"
            name="description"
            value={todoInfo.description}
            onChange={handleChange}
            className="input"
          />
          <button type="submit" className="todo-btn">
            āž• create todo
          </button>
        </form>
      </section>
    </section>
  );
};

export default CreateTodo;
Enter fullscreen mode Exit fullscreen mode

Now, import the useState from react and import axios from the axios package. Finally, The UpdateTodo component will have three properties._id ,closeHandler , updateHandler
The UpdateTodo component may look something like this.

Update Todo Component

//components/UpdateTodo.js

import { useState } from "react";
import axios from "axios";

function UpdateTodo({ _id, closeHandler, updateHandler }) {
  const [todoInfo, setTodoInfo] = useState({ title: "", description: "" });

  const handleChange = (e) => {
    setTodoInfo((data) => ({ ...data, [e.target.name]: e.target.value }));
  };

  const submitHanlder = (e) => {
    e.preventDefault();

    axios
      .put(`http://localhost:4000/api/todoapp/${_id}`, todoInfo)
      .then((res) => {
        setTodoInfo({ title: "", description: "" });
      })
      .catch((err) => {
        console.error(err);
      });
  };

  return (
    <form
      className="form-container"
      onSubmit={(e) => {
        submitHanlder(e);
        updateHandler();
        closeHandler();
      }}
    >
      <label htmlFor="title" className="label">
        Todo Title
      </label>
      <input
        type="text"
        name="title"
        className="input"
        onChange={handleChange}
      />
      <label htmlFor="description" className="label">
        Todo Description
      </label>
      <input
        type="textarea"
        name="description"
        className="input"
        onChange={handleChange}
      />
      <button type="submit" className="todo-btn">
        āž• Add
      </button>
    </form>
  );
}
export default UpdateTodo;
Enter fullscreen mode Exit fullscreen mode

ImportĀ theĀ UpdateTodoĀ componentĀ fromĀ UpdateTodo.jsĀ andĀ thenĀ declareĀ modalĀ withĀ theĀ useStateĀ hook which is setĀ toĀ falseĀ byĀ default.Ā The modal valueĀ willĀ beĀ eitherĀ trueĀ orĀ false.Ā TheĀ UpdateTodoĀ componentĀ willĀ beĀ renderedĀ conditionally ifĀ theĀ editĀ buttonĀ isĀ pressedĀ onĀ anyĀ ofĀ theĀ todo,Ā weĀ willĀ set setModalĀ toĀ trueĀ whenĀ theĀ UpdateTodoĀ componentĀ isĀ rendered,Ā andĀ thenĀ declareĀ idĀ usingĀ theĀ useStateĀ hook.Ā TheĀ _idĀ ofĀ theĀ todoĀ Ā thatĀ needsĀ toĀ beĀ updatedĀ willĀ beĀ saved.ItĀ willĀ beĀ passedĀ toĀ theĀ UpdateTodoĀ componentĀ asĀ aĀ prop. UseĀ theĀ useStateĀ hookĀ toĀ declareĀ anĀ update. ThisĀ willĀ beĀ usedĀ toĀ retrieveĀ allĀ ofĀ theĀ to-doĀ itemsĀ fromĀ theĀ database. When a todo document is updated, the update will toggle between true and false.Now, define a function editHandler this function will replace the state id with the document's _id and set the modal state to true. Next, create a function called updateHandler. If the todo has been updated by the user, this will invert the state of the update. Inverting the state will cause the useEffect hook to update the todo array. Finally, define a function closeHandler, which will be used to close the UpdateTodo component. This will set the id to an empty string and the modal property to false.

After you've made the changes, the DisplayTodo and TodoList should look like this.

//components/DisplayTodo.js

import { useState, useEffect } from "react";
import axios from "axios";
import { Link } from "react-router-dom";
import UpdateTodo from "../UpdateTodo";
import TodoLists from "../TodoLists";

export function DisplayTodo() {
  const [id, setId] = useState("");
  const [update, setUpdate] = useState(false);
  const [infoTodo, setInfoTodo] = useState([]);
  const [modal, setModal] = useState(false);

  useEffect(
    function () {
      axios
        .get("http://localhost:4000/api/todoapp")
        .then((res) => {
          setInfoTodo(res.data);
        })
        .catch((err) => {
          console.log(err.message);
        });
    },
    [update]
  );

  const editHandler = (e) => {
    setId(e.target.name);
    setModal(true);
  };

  const updateHandler = () => {
    setUpdate(!update);
  };

  const deleteHandler = (e) => {
    axios.delete(`http://localhost:4000/api/todoapp/${e.target.name}`);

    setInfoTodo((data) => {
      return data.filter((todo) => todo._id !== e.target.name);
    });
  };

  const closeHandler = () => {
    setId("");
    setModal(false);
  };

  return (
    <section className="container">
      <Link to="/add-list" className="button-new">
        <button className="todo-btn">āž• Add new todo</button>
      </Link>
      <section className="todo-data">
        <h1></h1>
        <ul className="todo-list-block">
          {infoTodo.map((todoInfo, index) => (
            <TodoLists
              key={index}
              todoInfos={todoInfo}
              editHandler={editHandler}
              deleteHandler={deleteHandler}
            />
          ))}
        </ul>
      </section>
      {modal ? (
        <section className="update-container">
          <div className="update-todo-data">
            <p onClick={closeHandler} className="close">
              &times;
            </p>

            <UpdateTodo
              _id={id}
              closeHandler={closeHandler}
              updateHandler={updateHandler}
            />
          </div>
        </section>
      ) : (
        ""
      )}
    </section>
  );
}

export default DisplayTodo;
Enter fullscreen mode Exit fullscreen mode

TodoList Component

//components/TodoList.js

import React from "react";

const TodoLists = ({ todoInfos, editHandler, deleteHandler }) => {
  const { _id, title, description } = todoInfos;

  return (
    <li key={_id}>
      <div className="title-description">
        <h2>{title}</h2>
        <h1></h1>
        <p>{description}</p>
      </div>
      <h1></h1>
      <div className="todo-btn-container">
        <button className="todo-btn" name={_id} onClick={editHandler}>
          šŸ–Šļø
        </button>
        <button className="todo-btn" name={_id} onClick={deleteHandler}>
          šŸ—‘ļø
        </button>
      </div>
    </li>
  );
};

export default TodoLists;
Enter fullscreen mode Exit fullscreen mode

Finally, let's incorporate some styles into our project. Now, go to your App.css file and update your style, or simply copy and paste the following CSS code.

https://gist.github.com/pramit-marattha/e88d83b66ce7ca9a01e840f486cf9fc8


Setting up Backend

Now,we'll start by setting up our backend with npm and installing relevant packages, then set up a MongoDB database, then set up a server with Node and Express, then design a database schema to define a Todo, and then set up API routes to create, read, update, and delete documents from the database.

Now go to your server directory and use the command prompt to run the code below.

npm init -y 
Enter fullscreen mode Exit fullscreen mode

Updating package.json

To install the dependencies, use the following instructions in the terminal.

npm install cors express dotenv mongoose nodemon
Enter fullscreen mode Exit fullscreen mode

Dependencies

Dependencies

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

{
  "name": "server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon main.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "cors": "^2.8.5",
    "dotenv": "^10.0.0",
    "express": "^4.17.1",
    "mongoose": "^6.0.12",
    "nodemon": "^2.0.14"
  }
}
Enter fullscreen mode Exit fullscreen mode

And also, remember to update the scripts as well.

Updating scripts

Structuring the folder inside the server:

  • configurations : Make a file called database.js in the config folder. The necessary code for connecting to the MongoDB database will be contained in this file.

  • controllers : The files in the controllersā€™ folder will contain the methods for the endpoints to interface with the database.

  • models : The files that specify the MongoDB schema will be found in the modelā€™s folder.

  • routers : The files with the endpoints will be found in the routers folder.

Folder structure

Configuring main.js

  • Import express module.
  • Use express() to start our app.
  • Using the app, create a get method for the endpoint http://localhost:4000.
  • For our server to run, set the PORT to 4000.
  • Using our app, you may listen to PORT.
const express = require("express");
const cors = require("cors");

const dotenv = require("dotenv");

dotenv.config();

const app = express();

const PORT = process.env.PORT || 5000;

// listen
app.listen(PORT, () =>
    console.log(`Server is running on http://localhost:${PORT}`)
);
Enter fullscreen mode Exit fullscreen mode

Now open your .env file, or create one if you don't have one, and paste the following code inside it.

PORT=4000
Enter fullscreen mode Exit fullscreen mode

Now use the following code to start the server with nodemon. Ensure that the following command is executed from the project directory.

npm start
Enter fullscreen mode Exit fullscreen mode

If the server has started successfully, the terminal should display the following message.

npm start

Getting started with MongoDB

So, what is MongoDB?

MongoDB is an open source, cross-platform document-oriented database program. MongoDB is a NoSQL database that uses JSON-like documents and optional schemas to store data. MongoDB is a database developed by MongoDB Inc. and published under the terms of the Server Side Public License.

Sign in to MongoDB

Sign in MongoDB

Make a new project.

Login mongodb

Create a Project

Creating Project

Building a database

Building database

Creating a cluster

Creating a clustre

Selecting a cloud service provider
Cloud service provider

Make a cluster and wait for the cluster to be built before proceeding (usually takes around 5 -10 minutes)

Making a clusture

Allow access from anywhere by clicking connect. Then IP address should be added.

Allow IP access

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

Creating a user

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

Insert mongodb+srv into the .env file.

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

Now open the database.js file located inside the configurations folder and make the modifications listed below.

Import Mongoose module.

Import Mongoose module

Import dotenv package and configure it. Create DATABASE_URL inside env file and add your credential inside it and then you are able to import it from the .env file.

env file

Importing dotenv module

Importing dotenv

Define the databaseConfiguration method for establishing a database connection.
The databaseConfiguration method should be exported and called in main.js.

db config

Now, database.js file should resemble something like this.

database

//database.js

const mongoose = require('mongoose');
const dotenv = require('dotenv');
dotenv.config();

const databaseURL = process.env.DATABASE_URL;

const databaseConfiguration = async () => {
  try {
    await mongoose.connect(databaseURL, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    });
    console.log('Database connected');
  } catch (err) {
    console.log(err);
    process.exit(1);
  }
};

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

Add the following changes on main.js file

main js

// main.js
const express = require('express');
const cors = require('cors');
const databaseConfiguration = require('./configurations/database.js');

const dotenv = require('dotenv');

dotenv.config();

const app = express();

const PORT = process.env.PORT || 5000;

//connecting to the mongodb database
databaseConfiguration();

// add the middlewares
app.use(express.json({ extended: false }));
app.get('/', (req, res) => res.send('<h1>Server is up and running</h1>'));

// listen
app.listen(PORT, () =>
  console.log(`Server is running on http://localhost:${PORT}`)
);
Enter fullscreen mode Exit fullscreen mode

Adding database schema:

Add a models.js file inside the models folder. We will define the entire database schema inside this particular file.

models js

// models.js
const mongoose = require('mongoose');

const TodoListSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
  },
  description: {
    type: String,
  },
  date: {
    type: Date,
    default: Date.now,
  },
});

const Todo = mongoose.model('todo', TodoListSchema);

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

Defining the entire endpoint of our API

Todo routes

//todo.routes.js

const express = require("express");

const router = express.Router();

const {
  listAllTodo,
  createOneTodo,
  updateOneTodo,
  deleteTodo,
} = require("../controllers/todo.controller.js");

router.get("/", listAllTodo);

router.post("/", createOneTodo);

router.put("/:id", updateOneTodo);

router.delete("/:id", deleteTodo);

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

Defining the methods for endpoint

The methods for the endpoints will be defined in the controllers folder and inside controllers.js file.

Now open the controllers.js file located inside the controllers folder and make the modifications listed below.
Controllers

//controllers.js

const AppTodo = require("../models/models.js");

exports.createOneTodo = (req, res) => {
  AppTodo.create(req.body)
    .then((todo) => {
      console.log({ todo });
      res.json({
        message: "Cheers!! You have successfully added TODO",
        todo,
      });
    })
    .catch((err) => {
      res.status(404).json({
        message: "Sorry your todo list cannot be added",
        error: err.message,
      });
    });
};

exports.listAllTodo = (req, res) => {
  AppTodo.find()
    .then((todo) => {
      console.log({ todo });
      res.json(todo);
    })
    .catch((err) => {
      res
        .status(404)
        .json({ message: "There isnt any todo available", error: err.message });
    });
};

exports.updateOneTodo = (req, res) => {
  AppTodo.findByIdAndUpdate(req.params.id, req.body)
    .then((todo) => {
      console.log({ todo });
      return res.json({
        message: "Cheers!! You have successfully updated TODO",
        todo,
      });
    })
    .catch((err) => {
      res.status(404).json({
        message: "Sorry your todo list cannot be updated",
        error: err.message,
      });
    });
};

exports.deleteTodo = (req, res) => {
  AppTodo.findByIdAndRemove(req.params.id, req.body)
    .then((todo) => {
      console.log({ todo });
      res.json({
        message: "Cheers!! You have successfully deleted your TODO",
        todo,
      });
    })
    .catch((err) => {
      res.status(404).json({
        message: "Sorry your todo is not there",
        error: err.message,
      });
    });
};
Enter fullscreen mode Exit fullscreen mode

methods

Finally, add the endpoint to the main.js file. Also, don't forget to include the cors so that we can make API calls from the frontend application. As a result, your main.js file should look something like this.

Main js

//main.js
const express = require("express");
const cors = require("cors");
const databaseConfiguration = require("./configurations/database.js");
const todo = require("./routes/todo.routes.js");

const dotenv = require("dotenv");

dotenv.config();

const app = express();

const PORT = process.env.PORT || 5000;

//connecting to  mongodb 
databaseConfiguration();

//adding cors
app.use(cors({ origin: true, credentials: true }));

// adding  middlewares
app.use(express.json({ extended: false }));
app.get("/", (req, res) =>
  res.send("Hello there!! Cheers !! The server is up and running")
);

// using the todo routes
app.use("/api/todoapp", todo);

// listen
app.listen(PORT, () =>
  console.log(`Server is running on http://localhost:${PORT}`)
);
Enter fullscreen mode Exit fullscreen mode

After restarting the server, you should see something similar to this:

server running

Finally, start both the client and the server, and you should see something similar to this.

Demo

The complete source code for the application can be found here:

https://github.com/pramit-marattha/MERN-awesome-crud

Conclusion

This blog tutorial demonstrated how to use the MERN stack to build a basic React Todo CRUD application. This concise guide went over the essential MERN stack topics one by one, focusing on each one carefully and discreetly. You learned how to establish a basic react app, style it with necessary npm packages, and make HTTP queries related to the crud app; we also set up the node back-end server in the react app using necessary npm packages. We used MongoDB to save and store the data and learned how to use the React platform to store the data, and this lesson might have been very helpful for you if you are new to MERN stack development. Cheers!!

Happy coding !!

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

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
apongpoh profile image
Apongpoh

Great! Thanks

Collapse
albencfc profile image
Albenis KĆ«rqeli

Useful post šŸ‘šŸ‘

Collapse
drsimplegraffiti profile image
Abayomi Ogunnusi

Thanks for this post