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 .
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 handlingmultipart/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.
Sponsored:
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
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
Get Cart items
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)
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. 👍
Sure, Thanks
Wow thats detailed good tutorial.
Thank you Andrew!
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...