16
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.
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.
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
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.
Now, within the client folder install the following dependencies.
npm i axios react-router-dom
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"
]
}
}
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
Now, within the src folder, make another folder called components, and within that folder, make three folder/components: DisplayTodo, CreateTodo, TodoLists and UpdateTodo.
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;
TodoList
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>
);
};
CreateTodo
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": ""
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;
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.
//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">
×
</p>
</div>
</section>
) : (
""
)}
</section>
);
};
export default DisplayTodo;
TodoList component should look something like this:
// 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;\
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.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;
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.
// 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">
×
</p>
</div>
</section>
) : (
""
)}
</section>
);
}
export default DisplayTodo;
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.
// 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;
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.
//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;
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">
×
</p>
<UpdateTodo
_id={id}
closeHandler={closeHandler}
updateHandler={updateHandler}
/>
</div>
</section>
) : (
""
)}
</section>
);
}
export default DisplayTodo;
//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;
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.
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
Updating package.json
To install the dependencies, use the following instructions in the terminal.
npm install cors express dotenv mongoose nodemon
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"
}
}
And also, remember to update the scripts as well.
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.
- 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}`)
);
Now open your .env file, or create one if you don't have one, and paste the following code inside it.
PORT=4000
Now use the following code to start the server with nodemon. Ensure that the following command is executed from the project directory.
npm start
If the server has started successfully, the terminal should display the following message.
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
Make a new project.
Create a Project
Building a database
Creating a cluster
Make a cluster and wait for the cluster to be built before proceeding (usually takes around 5 -10 minutes)
Allow access from anywhere by clicking connect. Then IP address should be added.
In the database, create a user. You'll need the username and password for the MongoDB URI and finally create a database user.
Now, select the Choose a connection method.
Connect your application by clicking on it and finally select the correct driver and version.
Insert mongodb+srv into the .env file.
PORT=4000
DATABASE_URL=mongodb+srv://pramit:<password>@cluster0.qjvl6.mongodb.net/myFirstDatabase?retryWrites=true&w=majority
Now open the database.js file located inside the configurations folder and make the modifications listed below.
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.
Importing dotenv module
Define the databaseConfiguration method for establishing a database connection.
The databaseConfiguration method should be exported and called in main.js.
Now, database.js file should resemble something like this.
//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;
Add the following changes on main.js file
// 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}`)
);
Add a models.js file inside the models folder. We will define the entire database schema inside this particular file.
// 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;
Defining the entire endpoint of our API
//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;
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.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,
});
});
};
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
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}`)
);
After restarting the server, you should see something similar to this:
Finally, start both the client and the server, and you should see something similar to this.
The complete source code for the application can be found here:
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]
16