DEV Community

Cover image for Build a Shopping Cart in Nodejs and React
Sunil Joshi
Sunil Joshi

Posted on • Edited on • Originally published at wrappixel.com

Build a Shopping Cart in Nodejs and React

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 out backend part built in Nodejs.

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
Enter fullscreen mode Exit fullscreen mode

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.

There are some online template store where you could get great free react dashboard like WrapPixel and AdminMart.

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>
Enter fullscreen mode Exit fullscreen mode

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 >
  )
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.


Sponsored:

React


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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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)
    }
  }
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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);
    }
  }
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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);
    }
  }
Enter fullscreen mode Exit fullscreen mode

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😁

Top comments (1)

Collapse
 
rajeshroyal profile image
Rajesh Royal • Edited

you could have used bootstrap as a dependency and import only the components needed to you from bootstraps scss files. BTW nice article