Hi Guys ! in this article we will be creating a Product Store Application ,using MongoDB and React. On the second part we will be building the same application using Stitch(Realm) , a server less platform for building application.
So what is MongoDB , MongoDB is database, word MongoDB comes from the word Humongous , which means huge. So MongoDB will be having collections(table for schema full)and each collections are having document(data for schema full).So in MongoDB, data are stored in binary form of json format called BSON. It’s is very flexible since it is schema less we can store any type of data in collection. MongoDB is managed by a very strong community called MongoDB/Atlas (Cloud) which is self managed enterprise. It’s also having cloud management and ops management. It having mongoDB compass portal for data visualization, BI connector , MongoDB Charts used for data science and Stitch for server less backend solution, which is having features like server less query API, databases triggers and real time sync .
Before start coding , we will create our MongoDB cluster and will create project on that cluster. So we have to go to the website of MongoDB: https://cloud.mongodb.com, here we have to login then have to create our own project. For creating project we have to click on New Project Button, Then a modal will popup there we have to give the name of our project and then have to add your teammate name whom we want to give access , incase if we are building any project in a group .Now if we click on the Create Project , new project will be created . Now we have to create cluster where our data will be stored. For that we have to click on Build a cluster button, We have to select AWS as our cloud provider. Then select the region which cover our country and choose cluster tier as M0 Sandbox which is totally free, then we have to provide a name to our cluster. Then if we click on Create Cluster button , a cluster will be created . So we have created project cluster for our application.
Now let’s start coding. So we will start with the backend setup from db.js.
In db.js first we are importing mongoDB . We are creating the MongoDB client called MongoClient , Then we are defining the mongoDB connection url , so from where we will get that ? we have to go to our mongoDB portal there will be a button connect , if we click on that there will be three options we have to select the second one ‘connect your application’ and then a url will be generated ,we have to just add the password and database name which we want to use to that url . Now we have to keep in mind the password which we will be adding is not the login password , we have to add the database access password. To get that password we have to go to the mongoDB portal and have to select Database Access from left navigation bar, Now there we have to add user by clicking on add new database user button, we have to provide username and password, we can also categories our user Database role and user privilege, we can keep Read and Write to any database option there for now . Then click on Add user. The password we provided there is the password we have to add in place of password in url . Now for the database name if we click on the collections button we will be redirecting to the page for adding database, we can create a database by clicking create database button. The name which we provide to that database, is that name which we have to add on the url . Now we are replacing password and database name placeholder with those values in the url and adding that url into our mongoUrl variable. initDb function is the function to initiate the data , if the data is already initialized it will show the message ‘Database is already initialized’ and will return a callback function with the db passed in argument. Then we are using connect() function with mongoDB url ,connect() function creates a connection to a MongoDB instance and returns the reference to the database. Once it is connected we are sending _db as the reference of the database and passing it to the callback function, and if there is any error we are sending the error into the callback function. getDb() function is used to get the database. Lastly we are import those two functions initDb and getDb so that we can use those in other files. Now let’s see the app.js file.
In auth.js , we will be defining the user authentication of the application .We are importing express.router, json web token and bcryptjs. jsonwebtoken is used to create json web token for the user login , whenever a user login we will be creating the json web token , once the token expires the user have to again login. bcryptjs is used to encrypt our password before storing it into our database. We are importing the initDb() and getDb() function from db.js. Then we are creating an instance of our Router. createToken() function is used to create json web token , we are setting the expiry duration to 1 hour , means a user can logged into our application for one hour , after that he/she have to login again. Now we are performing post request to the endpoint /login , So router.post is having 3 parameters request, response and next. We are fetching the data of email and password from request body. We are getting database from getDb() function , once we get the database. we are calling the db().collection to get the collection with name ‘users’ . users is the collection into which will be storing our user details. So when a user will try to login first we will check if that email matches with any of our user data. If it matches we will compare the entered password with the password stored in database using bcrypt.compare() function. So if the password is same the bcrypt.compare() function will return true, else it will return false. bcrypt.compare is a asynchronous function so we are adding .then if the returned response is false we will throw a error or if it is true we will create a token using createToken function other than that we will set response status code to 200 and on response json we will send message : ‘Authentication succeeded’ and json web token created when our user logged in. Now .catch is used to catch whatever error is thrown , so remember when our password is not matching we are throwing an error , catch function will catch that error. So once the password is not matching we are setting the http request status to 401 and in response json we are sending the message ‘Authentication failed, invalid username or password’. So this is how we are handling the login endpoint.
Now we will be looking into the signup endpoint. So in /signup we will be performing post request. Like login we are fetching email and password from the request body and hashing the password before storing it in database with bcrypt.hash() function , so in hash function we are passing two values actual password and the length of the hashed code. bcrypt.hash() function is an asynchronous function so in the .then method we are defining that if the password is hashed we will be storing the hashed password to our database. For storing the user data we are calling our getDb() function and the then inside our users collection in database we are inserting an object having email and password. Email contains the user email id and password contains the hashed password of that user. Once the user data is inserted into our database then we are creating our json web token, setting the response status to 201 and setting response json with an object having json web token and user email id. If there is any error in storing the data , we will be setting the http response status to 500 and will be setting response json with object having message ‘Creating the user failed’. Now let’s see our product routes.
In products.js we will be defining our products routes. We are first importing express router, mongoDB , and functions defined in db.js. Think back to when you were first introduced to the concept of decimals in numerical calculations. Doing math problems along the lines of 3.231 / 1.28 caused problems when starting out because 1.28 doesn’t go into 3.231 evenly. This causes a long string of numbers to be created to provide a more precise answer. In programming languages, we must choose which number format is correct depending on the amount of precision we need. When one needs high precision when working with BSON data type , the decimal128 is the one to use. As the name suggests, decimal128 provides 128 bits of decimal representation for storing really big (or really small) numbers when rounding decimals exactly is important. So for that we are importing mongodb.Decimal128 for string price value into our database. ObjectId is used to create id for each document of our collection database. We have created our router instance . In the base route / we will be showing products page, we will be doing get request .We have created an empty array products for storing the products. Now we are finding data from products collection of our database and sorting the products in ascending order, then for each product document in database we are converting the price parameter which is in integer into string , then we are pushing the product document into our empty products variable. Since it’s a asynchronous we are adding .then method , so once all the product document id pushed into products array , we are send the products array into the response json with http status of 200.But if there is any error we are setting the response status to 500 with message: ‘An error occurred’.
/:id route will be route to displaying particular product details, where id is the object id of that particular product. Now we are calling our getDb() function to get the database, then on our products collection we are performing get operation , we are finding product those ObjectId (_id) which matches with the id in the route(req.params.id), req.params.id actually contains the value of id in our route /:id. So when we get the product id matched we are converting the product price into string. Then we are setting the response status to 200 and in the json we are sending the product document. And if there are any error we are setting http status code 500 and in json we are sending message ‘An error occurred’.
Now we will be adding new product to our products list .So we are doing post request. So we will be creating our object newProduct which will be having all the details of the product which we want to add .Then we are calling getDb() function , we are insert newProduct object into our products collection , when insertion is complete we are setting http status to 201 and in the response we are sending message ‘Product added’ and product Id. If there is any error we are setting http request status 500 with json message ‘An error occurred’.
We can also edit existing products, for that we will be doing patch request on /:id , where id will be the id of the product which we will be editing. So we are doing post request. We are creating our object updatedProduct which will be having the editing parameter of the product. Then we are calling getDb() function , we are using updateOne function ,updateOne function will be having two parameter, first one contains the condition for the determining which product we want to edit on database, if we keep it blank object it will reflect those changes to all the documents inside the collection. So we are putting condition that we will modify that product those ObjectId (_id) is equal to request.params.id(id in the route/:id), then we are just replacing the entire document with the update data (updatedProduct) using set operator. Post and patch is almost same only it’s more relevant to use patch when we are modifying any pre-existing document. So once the modification is done we will be setting http status to 200 and in the response json we are passing message: ‘Product updated’ and product id , else we will be passing the error message : ‘An error occurred’ in response json.
We can also delete our products , for that we will be calling delete request on ‘/:id’ where id will be the object id of the product we want to delete. So we are calling getDb() function , then we are deleting the document those ObjectId (_id) matches with the request.params.id(id in the route/:id). Once deleting is done we are setting the http response status to 200 , and in response json we are sending the message: ‘Product deleted’. Else if there is any error we will be setting response status code to 500 and sending error message ‘Product deleted’. So our backend api is now ready . Now we will be looking into our frontend. So we will start with index.js.
So index.js is the main file of our frontend .We are importing react , react-dom and from react-router-dom we are destructing BrowserRouter. We are also importing the index.css file for styling and our App component. We will be wrapping our App component with BrowserRouter so that we can implement the routing in our application. App.js is the main file which will be having the child components, so inside the div having id root we are rendering our App component by wrapping in with BrowserRouter. Now let’s see the App.js.
So App.js is the root file of our application it will be having all the child components of our application. We are importing react, react-router dom for routing and axios for api calls. Also we are importing components like Header, Modal, Backdrop, pages like Products, Product, EditProduct and Auth. So Now inside App component first we are defining the states , we are having isAuth, authMode and error parameters in state . logoutHandler function handles the log out functionality of our application, isAuth state is required to determine whether the user is logged in or not. So on logoutHandler we are setting the isAuth value to false. authHandler is the function to handle the authentication of our application , So if user enter the email id and password, the state authMode is first checked if it is login then the authData or the details user have entered will be post to the backend route http://localhost:3100/login.but if the authMode value in signup the user details will be posted to http://localhost:3100/signup. So this authData will be there in the api (backend) on the request body. Now once the request is successful, the api will be returning a response authResponse , if the status of the authResponse is 200 or 201 , we are just storing the token we got from the response and finally setting the isAuth to true just to indicate that the user is now loggedin. Else if there is any error , we will calling error Handler function with the response error message, and setting the isAuth to false. authModeChangedHandler is the function to just toggle the state of authMode between login and signup. errorHandler sets the value of the error state with the message passed into it as argument. Now we will see the actual Dom structure of our application. Inside render we are first define the routes of our application. We used to define all the routes and redirects (if any) inside Switch tag. So inside Switch tag we are first defining the redirects for base route ‘/’ user will be redirected to ‘/products’ routes, from ‘/auth’ and signup also the user will be redirected to our products page. For path ‘/product/:mode’, mode value will be ‘add’ ,means for adding new products we will be using this route. So we are rendering our EditProductPage page and sending the response data and errorHandler function as props of the page. Similarly when the path is ‘/product/:id/;mode’ mode value will be ‘edit’ and id will be the object Id of the document (product) which we want to edit. Here also we are rendering EditProductPage page with the response data and errorHandler function as props of it. For route ‘products/:id’ id will be the object id of the particular product whose details will be shown into our product page, So there we are rendering our product page ProductPage with the response data and errorHandler function as props of it. ‘/products’ will be rendering our products page ( ProductPage ).But if our user is not authenticated means the isAuth state value is false. we will be defining a different route. We will redirecting our user from ‘/’ , ‘/product’ , ‘/products’ to ‘/auth’, we want our user to first login then continue with other pages. For the route ‘/auth’ we are rendering the our AuthPage , there we are passing our mode state , authHandler and authModeChangedHandler as props. Now we are finally rendering our Modal component which will appear whenever there is some error , there we are passing a custom title and errorHandler function as props and a paragraph as the child of it. Backdrop is used to make the background of our application blackish as the Modal opens. We have added our Header component , where we are passing isAuth state and logoutHandler function as props. And finally we are adding the routes variable which we have defined above.
So Now we will first look into our components. We will start with Backdrop .
In Backdrop.js we are just adding a div having the css class to add the blackish effect to entire application whenever error occurred and Modal component pops up.
In Button.js , we are creating the button of our application. We are just having a html button tag with some styles and props.children rendered inside it.
In Header.js we have imported react , destructured NavLink from react-router-dom. NavLink is used to implement navigation between the pages of our application. First we are defining the links variable, if user is authenticated , we are creating a unordered list having NavLink for our products page, addProduct page and a button for logout. But if user is not authenticated we have only one NavLink for Authentication in unordered list. Finally we are rendering our links variable inside header.
In input.js we are creating the input fields of our application. So we are defining inputElement variable based on the elType props value , if elType is equal to ‘textarea’ we are adding textarea with all the config as props and we are passing the props onChange function in our onChange props . If the value of elType is something else other than ‘textarea’ we will be adding input tag. Then we are returning the dom , so inside a div there will be a label and our inputElement.
In Modal.js , we are importing our Button component. So modal will be displayed when the props value of open is true. So we are appending the open class on modal whenever the open value is true. We are having header with title, and 2 sections inside first section we are rendering our children and inside second section we are rendering the button element onClick of the button we are calling onClose() function coming as props.
In Product.js, we are iterating through products props , and rendering the ProductItem component. In ProductItem we are passing product id as key , product name as title, product description as text , product price as price, product image as imageUrl , and onDeleteProduct function as onDelete props.
In ProductItem.js , We will be using article tag of html, so inside it we will be adding props.imageUrl as background Image of the first div. Then we are rendering product title, price and text. We are having three buttons Details, Edit and Delete. Details button is used to show the details of product, Edit and Delete is used to edit and delete that particular product. So for Details button we are using link tag to link it with the ‘/products/:product.id’ url. For Edit button we are linking it to ‘products/:product.id/edit’ .In backend the ‘edit’ will be denoted as mode value. For deleting product we are passing onDelete function with the product id on the onClick event of the delete button. Now let’s see the pages folder.
In Auth.js we are defining our authentication page. So state will be having two parameters email and password. inputChangeHandler is the function which is used to set our state values according to the values we will be entering in our input fields. Now we are checking value of mode for setting value of our modeButtonText and submitButtonText variable accordingly. This page will be having a button for login or signup, onClick of this we are calling onAuthModeChange function from props. Now inside the form we are having input fields for email and password. onChange of each input fields we are calling inputChangeHandler function with the input field name and event object. We are also having the button for submitting the form. onSubmit of the form we are calling onAuth function from props, with the object having input field values of email and password as the parameter . Now we will be looking into the products page.
In products.js we are importing the products component. Our page is having two parameters in state those are isLoading and products. Inside componentDidMount() we are calling the fetchData function. So we will first see the fetchData() function , this function is used to fetch data from our backend. In fetchData () function we are performing get operation on our products endpoint (http://localhost:3100/products) to get all the products . Once we get all the products we are setting the value of isLoading to false and we are passing the response data to our products state. If there is any error we will be setting isLoading to false and will call onError function from props with the error message .Inside render we are defining the content variable so initially our content will be a paragraph having ‘Loading products…’ once isLoading is false and there will be products is our products array , we will be setting the content as the our product component , on that product component we are sending our product state and productDeleteHandler function as props. If the state isLoading is false and there is no product into the products array we will be set our content to the paragraph having text ‘Found no products. Try again later’. productDeleteHandler() function is used to delete the product so whenever user click on the delete button , we will be calling this function. In productDeleteHandler() we are performing delete request on http://localhost:3100/products/' and passing the product id in the request body , once it is deleted we are calling the fetchData() function just to reflect the changes in our ui but if there is any error we will be calling onError function with the error message .Then we are returning our content inside main tag. Now we will be looking into the product details page.
In product.js we are designing our product details page. We have two parameter in states isLoading and product. So when the page loads in componentDidMount() we are fetching the details of the particular products .We are performing get function on the products endpoint http://localhost:3100/products/+ we are appending the id from the page url(this .props.match.params.id).Once we get the data of that particular product we are setting our isLoading state to false and passing the response data to our product state. If there is any error we will be setting isLoading state to false, and calling onError() function with error message. So we are rendering content variable , so initially our page will show ‘Is loading…’. If isLoading is false , and product details is fetched , we are setting content to main tag having product name, price, product image and description. If isLoading is false and there is no product details , we will show paragraph having text ‘Found no product. Try again later’. Now we will be seeing the Edit Product page.
In EditProduct.js , we are creating add product and edit product page of our application. We are importing our Input and Button component. So our ProductEditPage is having 5 parameters in state , isLoading , title, price, imageUrl and description. When our page renders , in componentDidMount we will be checking whether the user is adding new product or editing existing product for that we are checking this.props.match.params.mode if it is equal to ‘edit’, we will be performing get request on http://localhost:3100/products/+we are appending the product id of the product we are editing. Once we get all the details of our product we are setting our isLoading state to false, title to product name, price to product price , imageUrl to product.image and description to product description. If this.props.match.params.mode is not equal to ‘edit’ we will just set our isLoading to false. editProductHandler() function is used to edit or add products in our application. We will be first error checking if the user have not entered any thing and clicks on the submit button we will just simply return the function we will not perform anything. If user enters all the details and submit the form we will first set the isLoading state to true, we will create an object productData with all the input fields values. Then we will creating our request based on the value of mode present in our url (this.props.match.params.mode). If mode is equal to ‘edit’ we will be preforming patch request to the endpoint ‘http://localhost:3100/products/' + this.props.match.params.id’ and will send the productData in the request body. But if it is not then it means the user is actually adding new products for that we are performing post request to the endpoint ‘http://localhost:3100/products ’ and will send the productData in the request body. Once we get the response we will set isLoading state to false, and redirecting our page to products page. If there is any error , we are setting isLoading states to false and calling our onError() function with error message. inputChangeHandler() function is used to set our state value with the input field value. So inside render we will setting our content, So our content will be a form having input fields for product title, price, image, description. For each fields we are passing the config object, label, elType, and on onChange() function we are calling inputChangeHandler function with event object and label of each input field. Then we are having button to submit the edit /add product form , on submission of the form we are calling editProductHandler() function. If this.state.isLoading is true we will set content to paragraph having text ‘Is loading…’.And finally render our content inside main tag.
So our application is now ready but how we will running our application for that we will add our package.json , we just have to run npm init in our terminal , it will ask few questions but free to skip those by pressing enter. Then we will see our package.json will be created in our root folder. There we can add npm package as dependencies by running npm i <package name>. We have added few packages like axios, bcryptjs, body-parser and many more. So we will run npm i just to install all the dependencies from package.json. We will see node_modules folder will be created. Then we will run npm run start for running frontend , for running backend we will again open a new terminal and will run npm run start:server . Now let’s see our application.
So this is one way of creating a simple application using mongoDB as database and react as frontend. But it’s little bit complex we have to create the backend and frontend separately then have to connect and perform CRUD operations. In part 2 we will be building the same application with Stitch which is server less backend solution, which is having features like server less query API, databases triggers and real time sync .This will actually reduce our effort of creating application . Stitch takes care of most of the backend staffs. Till then we can go through the document of the mongoDB , it’s really nice and will give a descriptive overview of mongoDB concepts. I am attaching my github repo and mongoDB documentation link as the reference of this article.
Get started with MongoDB - MongoDB Documentation
Find the guides, samples, and references you need to use the database, visualize data, and build applications on the…
This project was bootstrapped with Create React App. Below you will find some information on how to perform common…