Building a Shopping Cart In Nodejs

Building a Shopping Cart In Nodejs

Written by Sunil Joshi on Aug 27th, 2020 Views Report Post

In this article we will be building an E-commerce platform with Nodejs as backend and for Frontend, we will have 3 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.

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 .

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

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

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}`);
});

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);
    });
}

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

require("./config/mongoose.js")(app);

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;

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
}

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
});

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"));

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;

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
        })
    }
}

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);
}

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

require('./app/routeHandler')(app)

Testing Our Routes

Getting All Products

Getting All Products

Creating A Post

Creating a Post

Get Product by ID

Remove Product

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);

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
}

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
            })
        }
    }

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;

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);
}

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, i am going write that in 3 different frontend technologies Vue, Angular and React, will post link here soon.

Comments (0)