DEV Community

Cover image for Building a Shopping Cart In Nodejs
Sunil Joshi
Sunil Joshi

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

Building a Shopping Cart In Nodejs

In this article we will be building an E-commerce platform with Nodejs as backend and for Frontend, we will have three different techonologies Angular, React and Vuejs. I will publish those articles and give link in this one soon. Vue Vite for frontend part is live, you can read now. You can now also check frontend part in react.

We will break down this article into two parts, the Backend and the Frontend. Our Application will have basic features like Adding of Product and Adding Product to cart.

Prerequisites

  • Familiarity with HTML, CSS, and Javascript (ES6+).
  • Vs code or any code editor installed on your development machine.
  • POSTMAN installed on your development machine.
  • Basic knowledge of Reactjs and Expressjs.

We will start by setting up the backend for our application. Let’s create a new directory for our application and initialize a new nodejs application. Open up your terminal and type the following:

cd desktop
mkdir reactcart && cd reactcart
npm init -y
code .
Enter fullscreen mode Exit fullscreen mode

Installing The Necessary Packages

We will have to install some packages for our application:

  • body-parser: is a piece of express middleware that reads a form’s input and stores it as a javascript object accessible through req.body.
  • nodemon : will watch our files for any changes and then restarts the server when any change occurs.
  • express This will be used to build our nodejs server.
  • cors : is a mechanism that uses additional HTTP headers to tell browsers to give a web application running at one origin, access to selected resources from a different origin.
  • dotenv : will store all of our environment variables. This is where we will store our email variables.
  • morgan: This is a package that will log all our application routes.
  • mongoose: An object modeling tool used to asynchronous query MongoDB.
  • multer:Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files.

To install this packages open your terminal and type:

npm i express mongoose morgan dotenv multer body-parser cors nodemon --save
Enter fullscreen mode Exit fullscreen mode

Running this command will create a node_modules folder.You have to create a .gitignore file and add the node_modules file inside it.


Sponsored:

VueJs


Setting Up the Server

We will continue by creating an src/index.js file and add the following lines of code:

const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const morgan = require('morgan');
const app = express();
app.use(morgan('dev'));
app.use(cors());
app.use(bodyParser.json())
app.get('/', (req, res) => {
    res.json({
        message: 'Arise MERN Developers'
    });
});
const port = process.env.PORT || 4000;
app.listen(port, () => {
    console.log(`Application is Running on ${port}`);
});
Enter fullscreen mode Exit fullscreen mode

After adding this, We can run our Application using Nodemon by typing nodemon src in our terminal. Running this will output Application is Running on 4000.

Now that our server is running, We have to setup our mongoDB server. To do this create a new directory src/config and create a mongoose.js file and add the following codes:

const mongoose = require("mongoose");
module.exports = app => {
    mongoose.connect('mongodb://localhost:27017/cart', {
        useUnifiedTopology: true,
        useNewUrlParser: true,
        useFindAndModify: false
    }).then(res => console.log("conneceted")).catch(err => console.log(err))
    mongoose.Promise = global.Promise;
    process.on("SIGINT", cleanup);
    process.on("SIGTERM", cleanup);
    process.on("SIGHUP", cleanup);
    if (app) {
        app.set("mongoose", mongoose);
    }
};
function cleanup() {
    mongoose.connection.close(function () {
        process.exit(0);
    });
}
Enter fullscreen mode Exit fullscreen mode

Now we need to register this config in our index.js file:

require("./config/mongoose.js")(app);
Enter fullscreen mode Exit fullscreen mode

Adding this will connect to our database when ever our Nodejs server is running.

Note that you have to declare this after you have declared the instance of express.

We have to now create our MongoDB models and routes for out Products and Cart.

Create an src/app directory, This is where we will be creating our modules. Inside this directory, Create a product directory and add the following file:

  • model.js
  • controller.js
  • repository.js
  • route.js

It’s also a good idea to take all DB communications to the repository file.

Lets define our product model by adding this to our model.js file:

const mongoose = require("mongoose");
const productSchema = mongoose.Schema({
  name: {
    type: String,
    required: [true, "Please Include the product name"],
  },
  price: {
    type: String,
    required: [true, "Please Include the product price"],
  },
 image: {
    type: String,
    required: true,
  },
});
const Product = mongoose.model("Product", productSchema);
module.exports = Product;
Enter fullscreen mode Exit fullscreen mode

Our product model will be basic as possible as it holds the product name, price and image.

We now need to define our DB requests in our repository.js file:

const Product = require("./model");
exports.products = async () => {
    const products = await Product.find();
    return products;
};
exports.productById = async id => {
    const product = await Product.findById(id);
    return product;
}
exports.createProduct = async payload => {
    const newProduct = await Product.create(payload);
    return newProduct
}
exports.removeProduct = async id => {
    const product = await Product.findByIdAndRemove(id);
    return product
}
Enter fullscreen mode Exit fullscreen mode

We need to define our basic routes to get all products, get single product details , remove product and create product. The logic is the routes will be talking to our controllers and the controller talks to the repository and the repository talks to our model.

Before we define our routes we need to configure multer for our image upload.Create a multer.js file and add the following code:

const multer = require("multer");
const path = require("path");
//image upload
const storage = multer.diskStorage({
    destination: (req, res, cb) => {
         cb(null, path.join("./files/"));
    },
    filename: (req, file, cb) => {
        cb(null, new Date().toISOString() + file.originalname);
    }
});
// checking file type
const fileFilter = (req, file, cb) => {
    if (file.mimetype.startsWith('image')) {
        cb(null, true);
    } else {
        cb(new Error('Not an image! Please upload an image.', 400), false);
    }
};
exports.upload = multer({
    storage: storage,
    limits: {
        fileSize: 1024 * 1024 * 6
    },
    fileFilter: fileFilter
});
Enter fullscreen mode Exit fullscreen mode

create a files directory in the root of your application.This is where all uploaded images will be stored.

Since all images goes to the files directory,We have to make that files folder.To do this head over to the index.js file and add this:

app.use('/files', express.static("files"));
Enter fullscreen mode Exit fullscreen mode

With this done, we can now serve images store in the files directory.

Add this to the routes.js file:

const router = require("express").Router();
const productController = require("./controller");
const multerInstance = require('../../config/multer')
router.post("/", multerInstance.upload.single('image'), productController.createProduct);
router.get("/", productController.getProducts);
router.get("/:id", productController.getProductById);
router.delete("/:id", productController.removeProduct);
module.exports = router;
Enter fullscreen mode Exit fullscreen mode

We now have to define the methods for this routes. To do that create add this to the controller.js file:

const productRepository = require('./repository')
exports.createProduct = async (req, res) => {
    try {
        let payload = {
            name: req.body.name,
            price: req.body.price,
            image: req.file.path
        }
        let product = await productRepository.createProduct({
            ...payload
        });
        res.status(200).json({
            status: true,
            data: product,
        })
    } catch (err) {
        console.log(err)
        res.status(500).json({
            error: err,
            status: false,
        })
    }
}
exports.getProducts = async (req, res) => {
    try {
        let products = await productRepository.products();
        res.status(200).json({
            status: true,
            data: products,
        })
    } catch (err) {
        console.log(err)
        res.status(500).json({
            error: err,
            status: false,
        })
    }
}

exports.getProductById = async (req, res) => {
    try {
        let id = req.params.id
        let productDetails = await productRepository.productById(id);
        res.status(200).json({
            status: true,
            data: productDetails,
        })
    } catch (err) {
        res.status(500).json({
            status: false,
            error: err
        })
    }
}
exports.removeProduct = async (req, res) => {
    try {
        let id = req.params.id
        let productDetails = await productRepository.removeProduct(id)
        res.status(200).json({
            status: true,
            data: productDetails,
        })
    } catch (err) {
        res.status(500).json({
            status: false,
            error: err
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

Create a routerHandler.js file inside the src directory,This will be our global routes handler:

const productRoutes = require("./Product/routes")
module.exports = app => {
    app.use("/product", productRoutes);
}
Enter fullscreen mode Exit fullscreen mode

Then register it in the index.js file. Make sure to register this file after the mongoose instance.

require('./app/routeHandler')(app)
Enter fullscreen mode Exit fullscreen mode

Testing Our Routes

Getting All Products

Getting All Products

Creating A Post

Creating a Post

Get Product by ID

image

Remove Product

image

We can now start working on our cart features. Create a new directory Cart inside the src/app directory.Just like we did for the Products module we will define the model,routes,repostory and controller files.

Lets start by defining our cart models:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let ItemSchema = new Schema({
    productId: {
        type: mongoose.Schema.Types.ObjectId,
        ref: "Product",
    },
    quantity: {
        type: Number,
        required: true,
        min: [1, 'Quantity can not be less then 1.']
    },
    price: {
        type: Number,
        required: true
    },
    total: {
        type: Number,
        required: true,
    }
}, {
    timestamps: true
})
const CartSchema = new Schema({
    items: [ItemSchema],
    subTotal: {
        default: 0,
        type: Number
    }
}, {
    timestamps: true
})
module.exports = mongoose.model('cart', CartSchema);
Enter fullscreen mode Exit fullscreen mode

Here we create our first schema to hold the instance of our current product and the create the second file which will hold the array of items in our cart.

Now we have to define our repository.js file:

const Cart = require("./model");
exports.cart = async () => {
    const carts = await Cart.find().populate({
        path: "items.productId",
        select: "name price total"
    });;
    return carts[0];
};
exports.addItem = async payload => {
    const newItem = await Cart.create(payload);
    return newItem
}
Enter fullscreen mode Exit fullscreen mode

Basically we write two methods that will get all cart items in our database and add an item to the cart model.

We can now create our controllers for our cart, We will have 3 controllers:

  • Get All cart items
  • Add product items to cart
  • Empty cart
    const cartRepository = require('./repository')
    const productRepository = require('../Product/repository');

    exports.addItemToCart = async (req, res) => {
        const {
            productId
        } = req.body;
        const quantity = Number.parseInt(req.body.quantity);
        try {
            let cart = await cartRepository.cart();
            let productDetails = await productRepository.productById(productId);
                 if (!productDetails) {
                return res.status(500).json({
                    type: "Not Found",
                    msg: "Invalid request"
                })
            }
            //--If Cart Exists ----
            if (cart) {
                //---- check if index exists ----
                const indexFound = cart.items.findIndex(item => item.productId.id == productId);
                //------this removes an item from the the cart if the quantity is set to zero,We can use this method to remove an item from the list  -------
                if (indexFound !== -1 && quantity <= 0) {
                    cart.items.splice(indexFound, 1);
                    if (cart.items.length == 0) {
                        cart.subTotal = 0;
                    } else {
                        cart.subTotal = cart.items.map(item => item.total).reduce((acc, next) => acc + next);
                    }
                }
                //----------check if product exist,just add the previous quantity with the new quantity and update the total price-------
                else if (indexFound !== -1) {
                    cart.items[indexFound].quantity = cart.items[indexFound].quantity + quantity;
                    cart.items[indexFound].total = cart.items[indexFound].quantity * productDetails.price;
                    cart.items[indexFound].price = productDetails.price
                    cart.subTotal = cart.items.map(item => item.total).reduce((acc, next) => acc + next);
                }
                //----Check if Quantity is Greater than 0 then add item to items Array ----
                else if (quantity > 0) {
                    cart.items.push({
                        productId: productId,
                        quantity: quantity,
                        price: productDetails.price,
                        total: parseInt(productDetails.price * quantity)
                    })
                    cart.subTotal = cart.items.map(item => item.total).reduce((acc, next) => acc + next);
                }
                //----if quantity of price is 0 throw the error -------
                else {
                    return res.status(400).json({
                        type: "Invalid",
                        msg: "Invalid request"
                    })
                }
                let data = await cart.save();
                res.status(200).json({
                    type: "success",
                    mgs: "Process Successful",
                    data: data
                })
            }
            //------------ if there is no user with a cart...it creates a new cart and then adds the item to the cart that has been created------------
            else {
                const cartData = {
                    items: [{
                        productId: productId,
                        quantity: quantity,
                        total: parseInt(productDetails.price * quantity),
                        price: productDetails.price
                    }],
                    subTotal: parseInt(productDetails.price * quantity)
                }
                cart = await cartRepository.addItem(cartData)
                // let data = await cart.save();
                res.json(cart);
            }
        } catch (err) {
            console.log(err)
            res.status(400).json({
                type: "Invalid",
                msg: "Something Went Wrong",
                err: err
            })
        }
    }
    exports.getCart = async (req, res) => {
        try {
            let cart = await cartRepository.cart()
            if (!cart) {
                return res.status(400).json({
                    type: "Invalid",
                    msg: "Cart Not Found",
                })
            }
            res.status(200).json({
                status: true,
                data: cart
            })
        } catch (err) {
            console.log(err)
            res.status(400).json({
                type: "Invalid",
                msg: "Something Went Wrong",
                err: err
            })
        }
    }

    exports.emptyCart = async (req, res) => {
        try {
            let cart = await cartRepository.cart();
            cart.items = [];
            cart.subTotal = 0
            let data = await cart.save();
            res.status(200).json({
                type: "success",
                mgs: "Cart Has been emptied",
                data: data
            })
        } catch (err) {
            console.log(err)
            res.status(400).json({
                type: "Invalid",
                msg: "Something Went Wrong",
                err: err
            })
        }
    }
Enter fullscreen mode Exit fullscreen mode

The code snippet has been commented for ease and better understanding.

We can now define our module routes and then define the global routes.Add this to the routes.js file:

const router = require("express").Router();
const cartController = require("./controller");
router.post("/", cartController.addItemToCart);
router.get("/", cartController.getCart);
router.delete("/empty-cart", cartController.emptyCart);
module.exports = router;
Enter fullscreen mode Exit fullscreen mode

And then update the routeHandler.js file to this:

const productRoutes = require("./Product/routes");
const cartRoutes = require('./Cart/routes')
module.exports = app => {
app.use("/product", productRoutes);
app.use("/cart", cartRoutes);
}
Enter fullscreen mode Exit fullscreen mode




Testing The cart features

Adding Item to cart

Adding Item to Cart

Get Cart items

Get Cart Items

Empty Cart

Empty Cart

For testing Purposes, Create some products using POSTMAN. This is what we will be using in our Frontend Application for testing purposes.

Exercise

  • Add Subtract Product Quantity from Cart
  • Remove Single Product From Cart

After implementing this,Push your work to git and add the Link in the comment section. Lets Have some Fun😁

Now that our backend is ready we can now move on to our frontend. For frontend, we have three different frontend technologies articles Vue Vite, Angular and React. I hope it will be useful for sure. Thanks for Reading!

Top comments (5)

Collapse
 
thinkverse profile image
Kim Hallberg

Quick tip here, you can add syntax highlighting to code-blocks by adding the name after the first three back-ticks, It's also mentioned in the cheatsheet#code. 👍

Collapse
 
suniljoshi19 profile image
Sunil Joshi

Sure, Thanks

Collapse
 
andrewbaisden profile image
Andrew Baisden

Wow thats detailed good tutorial.

Collapse
 
suniljoshi19 profile image
Sunil Joshi

Thank you Andrew!

Collapse
 
suniljoshi19 profile image
Sunil Joshi

For frontend our Vue Vite version is ready to build a shopping cart in Nodejs and Vue Vite - You can read from here - wrappixel.com/build-a-shopping-car...