My FeedDiscussionsHeadless CMS
New
Sign in
Log inSign up
Learn more about Hashnode Headless CMSHashnode Headless CMS
Collaborate seamlessly with Hashnode Headless CMS for Enterprise.
Upgrade ✨Learn more
Build a shopping cart in Nodejs and React

Build a shopping cart in Nodejs and React

Sunil Joshi's photo
Sunil Joshi
·Sep 5, 2020·

31 min read

In this article we are going to build shopping cart frontend for our application. We will be using React for building our frontend.

You can check our backend part built in Nodejs, which we already have published.

As far as we can, this will be minimal for full understanding of the main functionality.

To begin, we need to setup our React application using create-react-app.

npx create-react-app shopping-cart
cd shopping-cart
code .
npm start

You might need to install the react CLI first on your local machine if you haven’t before.

The code . command opens the project in visual studio code.

We can now discard the things we don’t need in App.js, and also get rid of the files (App.css and index.css).

To the main work

First, we create a components folder; this houses our reusable components, for example Navbar.

We will continue by setting up our user interface for the application. You can get all our UI components from WrapPixel's UI Kit.

WrapPixel is an online template store where you could get great react dashboard and react bootstrap templates.

We will add the bootstrap CDN into our root index.html file inside the public directory.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Web site created using create-react-app" />
  <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
    integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
  <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
  <title>Shopping cart</title>
</head>
<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
</body>
</html>

Then we add a Navbar.js file to the components folder. This is where we will handle our routing.

import React from "react"
import {
  Link
} from "react-router-dom"
export const Navbar = () => {
  return ( <
    nav className = "navbar navbar-expand-lg navbar-light bg-info" >
    <
    div className = "container" >
    <
    Link to = "/"
    className = "navbar-brand" > Vue Cart < /Link>
    <
    div className = "collapse navbar-collapse justify-content-end"
    id = "navbarNav" >
    <
    ul className = "navbar-nav" >
    <
    li className = "nav-item active" >
    <
    Link to = "/"
    className = "nav-link" > Home < /Link> < /
    li > <
    li className = "nav-item" >
    <
    Link to = "/cart"
    className = "nav-link" > Cart < /Link> < /
    li > <
    /ul> < /
    div > < /div> < /
    nav >
  )
}

Remember that we are using react-router-dom to route pages, so we need to add Navbar below our switch, as seen below ‘App.js’

import React from "react"
import { Switch } from "react-router-dom"
import { Navbar } from "./components/Navbar"

import "./App.css"
function App() {
  return (
    <div className='App'>
      <Navbar />
      <Switch>
        // Our pages will go here
      </Switch>
    </div>
  )
}
export default App

Secondly, we create a page folder. This folder houses our pages(product and cart page), all our service and views will be rendered in the pages for routing.

Let’s create a simple Product.js

import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
export const Products = () => {
  const [products, setProducts] = useState([]);
  const [hasError, setError] = useState(false);
  async function fetchData() {
    const res = await fetch("http://localhost:4000/product");
    res
      .json()
      .then((res) => {
        console.log(res.data);
        setProducts(res.data);
      })
      .catch((error) => {
        setError(error);
      });
  }
  async function addToCart(id, quantity) {
    try {
      const response = await fetch("http://localhost:4000/cart", {
        method: "POST",
        body: JSON.stringify({
          productId: id,
          quantity: quantity,
        }),
        headers: {
          "Content-type": "application/json; charset=UTF-8",
        },
      });
      let data = await response.json();
      alert("Item Added To Cart");
      console.log(data);
    } catch (err) {
      alert("Something Went Wrong");
      console.log(err);
    }
  }
  useEffect(() => {
    fetchData();
  }, []);
  console.log(products);
  return (
    <main>
      <section>
        <div className="banner-innerpage">
          <div className="container">
            <div className="row justify-content-center">
              <div className="col-md-6 align-self-center text-center">
                <h1 className="title">Shop listing</h1>
                <h6 className="subtitle op-8">
                  We are small team of creative people working together
                </h6>
              </div>
            </div>
          </div>
        </div>
      </section>
      <section>
        <div className="spacer">
          <div className="container">
            <div className="row mt-5">
              <div className="col-lg-9">
                <div className="row shop-listing">
                  {products.map((product, i) => (
                    <div className="col-lg-4">
                      <div className="card shop-hover border-0">
                        <img
                          src={"http://localhost:4000/" + product.image}
                          alt="wrapkit"
                          className="img-fluid"
                        />
                        <div className="card-img-overlay align-items-center">
                          <button
                            onClick={(e) => addToCart(product._id, 1)}
                            className="btn btn-md btn-info"
                          >
                            Add to cart
                          </button>
                        </div>
                      </div>
                      <div className="card border-0">
                        <h6>
                          <a href="#" className="link">
                            {product.name}{" "}
                          </a>
                        </h6>
                        <h6 className="subtitle">by Wisdom</h6>
                        <h5 className="font-medium m-b-30">
                          $195 /{" "}
                          <del className="text-muted line-through">$225</del>
                        </h5>
                      </div>
                    </div>
                  ))}
                </div>
              </div>
            </div>
          </div>
        </div>
      </section>
    </main>
  );
};

Noticed the fetchData function? We make a http request to the backend to list all products and store in the variable products (we are using React hooks, remember).

Since we have it as an array now, we loop through it to display as seen on line 64.

We will also need to add items to cart, which will be an async method making a request to the backend with its parameters passed to it. This is a very important feature as well.

The addToCart is defined on line 18:

  async function addToCart(id, quantity) {
    try {
      const response = await fetch("http://localhost:4000/cart", {
        method: "POST",
        body: JSON.stringify({
          productId: id,
          quantity: quantity,
        }),
        headers: {
          "Content-type": "application/json; charset=UTF-8",
        },
      })
      let data = await response.json()
      console.log(data)
    } catch (err) {
      console.log(err)
    }
  }

After that, We add the event listener to the button to call the addToCart button

 <button
                            onClick={(e) => addToCart(product._id, 1)}
                            className="btn btn-md btn-info"
                          >
                            Add to cart
                          </button>

Here, we pass the id of the product and a default quantity as 1.

Then add to our App.js once again, as a page.

import React from "react"
import { Switch, Route } from "react-router-dom"
import { Products } from "./pages/product"

import { Navbar } from "./components/Navbar"

import "./App.css"
function App() {
  return (
    <div className='App'>
      <Navbar />
      <Switch>
        <Route exact path='/' component={Products} />
      </Switch>
    </div>
  )
}
export default App

Shop Listing

Let’s head over to cart page and add our simple UI.

We then add a method to fetch a list of our cart items from the backend. Notice the fetchCart method below. Doing as pleased.

If you are not familiar with react hooks, you could always look it up or better still use the component based.

import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import "./cart.css";
export const Cart = (props) => {
  const [carts, setCarts] = useState([]);
  const [payload, setPayloader] = useState({});
  const [hasError, setError] = useState(false);
  async function fetchCart() {
    const res = await fetch("http://localhost:4000/cart");
    res
      .json()
      .then((res) => {
        console.log(res.data.items);
        setCarts(res.data.items);
        setPayloader(res.data);
      })
      .catch((error) => {
        setError(error);
      });
  }
  async function increaseQty(id) {
    try {
      const res = await fetch("http://localhost:4000/cart", {
        method: "POST",
        body: JSON.stringify({
          productId: id,
          quantity: 1,
        }),
        headers: {
          "Content-type": "application/json; charset=UTF-8",
        },
      });
      console.log(res);
      fetchCart();
      alert("Item Increamented");
    } catch (err) {
      console.log(err);
    }
  }
  async function emptyCart() {
    try {
      const res = await fetch("http://localhost:4000/cart/empty-cart", {
        method: "DELETE",
      });
      await res.json();
      fetchCart();
      props.history.push("/");
    } catch (err) {
      console.log(err);
    }
  }
  useEffect(() => {
    fetchCart();
  }, []);
  return (
    <main>
      <section>
        <div className="banner-innerpage">
          <div className="container">
            <div className="row justify-content-center">
              <div className="col-md-6 align-self-center text-center">
                <h1 className="title">Cart Listing</h1>
                <h6 className="subtitle op-8">
                  We are small team of creative people working together
                </h6>
              </div>
            </div>
          </div>
        </div>
      </section>
      <section>
        <div className="spacer">
          <div className="container">
            <div className="row mt-5">
              <div className="col-lg-9">
                <div className="row shop-listing">
                  <table className="table shop-table">
                    <tr>
                      <th className="b-0">Name</th>
                      <th className="b-0">Price</th>
                      <th className="b-0">Quantity</th>
                      <th className="b-0 text-right">Total Price</th>
                    </tr>
                    {carts.map((item, i) => (
                      <tr>
                        <td>{item.productId.name}</td>
                        <td>{item.productId.price}</td>
                        <td>
                          <button
                            onClick={(e) => increaseQty(item.productId._id)}
                            className="btn btn-primary btn-sm"
                          >
                            +
                          </button>
                          {item.quantity}
                          <button className="btn btn-primary btn-sm">-</button>
                        </td>
                        <td className="text-right">
                          <h5 className="font-medium m-b-30">{item.total}</h5>
                        </td>
                      </tr>
                    ))}
                    <tr>
                      <td colspan="3" align="right">
                        Subtotal :{payload.subTotal}
                      </td>
                      <td colspan="4" align="right">
                        <button
                          className="btn btn-danger"
                          onClick={(e) => emptyCart()}
                        >
                          Empty cart
                        </button>
                      </td>
                    </tr>
                  </table>
                </div>
              </div>
            </div>
          </div>
        </div>
      </section>
    </main>
  );
};

We can then loop through the array(cart) and modify.

Notice the increamentQty(id) method which takes the product id as a parameter and then set the quantity to one as default as we are updating the quantity by one.

 async function increaseQty(id) {
    try {
      const res = await fetch("http://localhost:4000/cart", {
        method: "POST",
        body: JSON.stringify({
          productId: id,
          quantity: 1,
        }),
        headers: {
          "Content-type": "application/json; charset=UTF-8",
        },
      });
      console.log(res);
      fetchCart();
      alert("Item increamented");
    } catch (err) {
      console.log(err);
    }
  }

After doing that we added click event to our button to trigger the method:

  <button
                            onClick={(e) => increaseQty(item.productId._id)}
                            className="btn btn-primary btn-sm"
                          >
                            +
                          </button>

Clicking on the button will increment the quantity of the item.

We then define an emptyCart method as well to delete all items currently in the cart. See below;

 async function emptyCart() {
    try {
      const res = await fetch("http://localhost:4000/cart/empty-cart", {
        method: "DELETE",
      });
      await res.json();
      fetchCart();
      props.history.push("/");
    } catch (err) {
      console.log(err);
    }
  }

Shopping Cart

Exercise

  • Implement the decrement feature
  • Implement remove product from cart

After implementing this, Push your work to git and add the link in the comment section. Lets have some fun😁