Let’s Add Products for our eCommerce App with Vue.js

We are building a complete E-commerce application from scratch. Today we are going to add the Products feature.

Alt Text

Introduction

As part of building an E-Commerce application, previously we built the backend for UserProfiles and created android UI for the same.

Let’s Develop an E-Commerce Application From Scratch Using Java and Spring

Let’s Build User Profile UI With Android for our E-Commerce App

The most important part of an online shop is products. We are going to create product backend APIs and consume those APIs in our web front-end built using the shiny Vue.js framework.

Demo

Webtutsplus E-commerce App

Database Design

This is a sample JSON of a product, we will link it to a category and add other fields in later tutorials.

[  {
    "id": 1,
    "name": "Adidas Final UCL Ball",
    "imageURL": "https://images.unsplash.com/photo-1589487391730-58f20eb2c308?ixid=MXwxMjA3fDB8MHxzZWFyY2h8OXx8Zm9vdGJhbGx8ZW58MHx8MHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60",
    "price": 3000.0,
    "description": "This is a football"
  },
  {
    "id": 2,
    "name": "Acer Nitro 5",
    "imageURL": "https://images.unsplash.com/photo-1496181133206-80ce9b88a853?ixid=MXwxMjA3fDB8MHxzZWFyY2h8M3x8bGFwdG9wfGVufDB8fDB8&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60",
    "price": 55000.0,
    "description": "This is a laptop"
  }
 ]
 CREATE TABLE `products` (
  `id` bigint NOT NULL,
  `description` varchar(255) DEFAULT NULL,
  `imageurl` varchar(255) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `price` double NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

Each product has a unique ID, name, image URL, price, description. We can model it in Springboot as

@Entity
@Table(name = "products")
public class Product {
    private @GeneratedValue @Id long id;
    private @NotNull String name;
    private @NotNull String imageURL;
    private @NotNull double price;
    private @NotNull String description;
    ...
}

API Design

In the real-world, we need to see the products, create new products, and update the products. So we will create 3 APIs. Later we will create many other real-world features like deleting, stocks, linking to categories, and adding tags. After this, we will create the UI.

package com.webtutsplus.ecommerce.controller;

import com.webtutsplus.ecommerce.common.ApiResponse;
import com.webtutsplus.ecommerce.model.Product;
import com.webtutsplus.ecommerce.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;

@RestController
@RequestMapping("/product")
public class ProductController {
    @Autowired ProductService productService;

    @GetMapping("/")
    public ResponseEntity<List<Product>> getProducts() {
        List<Product> body = productService.listProducts();
        return new ResponseEntity<List<Product>>(body, HttpStatus.OK);
    }

    @PostMapping("/add")
    public ResponseEntity<ApiResponse> addProduct(@RequestBody @Valid Product product) {
        productService.addProduct(product);
        return new ResponseEntity<ApiResponse>(new ApiResponse(true, "Product has been added"), HttpStatus.CREATED);
    }

    @PostMapping("/update/{productID}")
    public ResponseEntity<ApiResponse> updateProduct(@PathVariable("productID") long productID, @RequestBody @Valid Product product) {
        productService.updateProduct(productID, product);
        return new ResponseEntity<ApiResponse>(new ApiResponse(true, "Product has been updated"), HttpStatus.OK);
    }
}

Alt Text

You can find the backend code in this branch

Vue.js introduction

We will use Vue.js 3 to design the front end for displaying products of our E-commerce app.

Vue is a JavaScript framework that is used to build user interfaces. The official page of Vue describes it as a “progressive framework” which is “incrementally adoptable”. Both of these terms roughly relate to the same meaning. Vue can be used in two ways. We can either create a full Vue website, commonly known as a Single-page application or we can create stand-alone widgets (also known as components) which we can then use in our HTML as an addition. This way, the adoption of Vue in our application is totally in our hands. In this tutorial, we will be creating a Vue component to display products of our E-Commerce App.

Vue.js Requirements

As we are just creating a single widget for displaying the product and its details, we can make use of the Vue CDN from its installation page and carry on. Using Vue CDN is a beginner-friendly way to start our journey. For bigger projects, a different setup using Vue CLI is recommended. As a code editor, I will be using VS Code, but you can use any editor of your choice. First, we will create the following three files.

Alt Text

Let’s get started

First, let us set up the boilerplate for index.html. Our very first requirement is Vue CDN. Apart from this, I am also including Bootstrap 4 CDN and some Google fonts which I will be using. And for my custom styles, I am also including the link to my external CSS file. Also, include the script tag to link our JavaScript file app.js in the body tag. After all these things are done, our index.html will look like this:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Webtutsplus Market</title>
    <!-- The Vue 3.0.2 CDN -->
    <script src="https://unpkg.com/vue@3.0.2"></script>
    <!-- Bootstrap 4 CDN -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <!-- Google fonts -->
    <link rel="preconnect" href="https://fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css2?family=Courgette&display=swap" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;1,300;1,400&display=swap" rel="stylesheet">
    <!-- External Style sheet -->
    <link rel="stylesheet" href="./styles.css"></link>
</head>
<body>
    <script src = "app.js"></script>
</body>
</html>

We will revisit this file once we are done with our app.js file.

You can find the complete code here

Creating a Vue Component

Now it's time for our app.js file, where we will be writing the logic for creating our Vue Component. To create a component, Vue provides us with a method createApp(). This method takes as a parameter a component instance. This instance is made up of various component options and Lifecycle hooks that add user-defined behaviour to the component. Component Options

We will be using two of the component options — data and methods.

  • The data option lists all the data fields which are exposed by our component instance.
  • The methods option lists all the functions which we might need to use. For example, here we can define a function to execute whenever some button in our component template is clicked.

Lifecycle Hooks

Apart from these component options, our component instance also consists of Lifecycle hooks. Lifecycle hooks are the functions that our component automatically runs at different stages of its initialization. These stages include when a component is created, added to the DOM, updated, or destroyed. We will be using the lifecycle hook, mounted. This function is called when our component is added to the DOM. In this function, we can make HTTP requests to fetch data for our component.

Our Component

So in our case, the data option consists of six fields. A product array to store our products, showAddForm(a boolean value) to decide whether to display form for adding a new product and four-string data fields which we will use to get input from the user for product edit functionality.

data() {
    return {
        products : null,
        showAddForm : false,
        tempName : "",
        tempDescription : "",
        tempPrice : "",
        tempImageURL : "" 
    }
}

In methods option, we have five methods,resetInputFields, addProductButtonPressed, addProduct, editButtonPressed and editProduct.

  • resetInputFields - it sets the values of all data fields bound to the input form to an empty string.
  • addProductButtonPressed - it toggles the display of form used for adding a new product. It gets executed whenever the user clicks the “Add a New Product” button.
  • addProduct - it creates a new product as per the user input and makes a POST request to backend API to add this product. It gets executed when the user submits the form for adding a new product.
  • editButtonPressed - it toggles the form for editing a product based on the product’s showEditForm property and sets the values of input fields as per the invoking product details. It gets executed when the user clicks the “Edit” button. For distinguishing the product, this method takes the index of the product as a method argument.
  • editProduct- it creates a new product as per the user input and makes a POST request to backend API to update the product details. This method also takes the index of the product as a method argument. It gets executed when the user submits the form for editing a product. The backend API makes use of the product’s unique id to update its details.

In mounted life cycle hook, we fetch all the products from the E-Commerce API and store them in the products array. In addition to this, we also add a property showEditForm to all products and set it to false.

mounted : async function mounted(){
    const response  = await fetch(urlGetProduct);
    this.products = await response.json();
    for(product of this.products) {
        product.showEditForm = false;
    }
}

Now that our component is created we have to add this component to our DOM. For this, we make use of the mount method.

app.mount("#app");

This statement mounts our component to the HTML element with id “app”. Now our Vue component controls everything inside this element. We can make this HTML element to render dynamic data, add interactivity and handle events occurring inside it. This completes the creation of our Vue component in app.js.

const baseURL = "http://remotedevs.org:8080/api/"
const urlGetProduct = baseURL + "product/";
const urlUpdateProduct = baseURL + "product/update/";
const urlAddProduct = baseURL + "product/add"

const app = Vue.createApp({
    data() {
        //data fields
        return {
            products : null,
            showAddForm : false,
            //fields for form inputs
            tempName : "",
            tempDescription : "",
            tempPrice : "",
            tempImageURL : "" 
        }
    },
    methods:{
        resetInputFields : function() {
            this.tempName="";
            this.tempDescription="";
            this.tempPrice="";
            this.tempImageURL="";
        },
        //ADD Product Functionality
        addProductButtonPressed : function() {
            this.showAddForm = !this.showAddForm;
            this.resetInputFields();
        },
        addProduct : async function() {
            this.showAddForm = false;
            const newProduct = {
                id : this.products.length+1,
                name : this.tempName,
                description : this.tempDescription,
                price : this.tempPrice,
                imageURL : this.tempImageURL
            }
            //the POST request to API
            await fetch(urlAddProduct, {
                method : "POST",
                body : JSON.stringify(newProduct),
                headers: {
                    'Content-Type': 'application/json'
                }
            })
            .then((res) => {
                this.products.push(newProduct);
                alert("Product Added Successfully!")
            })
            .catch((err) => console.log(err));   
        },
        //EDIT Product Functionality
        editButtonPressed : function(index) {
            const curProduct = this.products[index];
            //toggle the form
            curProduct.showEditForm = !curProduct.showEditForm;
            //setting the input fields as per current product
            this.tempName = curProduct.name;
            this.tempDescription = curProduct.description;
            this.tempPrice = curProduct.price;
            this.tempImageURL = curProduct.imageURL;
        },
        editProduct : async function(index) {
            //close the form after submit is clicked
            this.products[index].showEditForm = false;
            //creating the new product
            const newProduct = {
                id : this.products[index].id,
                name : this.tempName,
                description : this.tempDescription,
                price : this.tempPrice,
                imageURL : this.tempImageURL
            }
            //updating the current object
            this.products[index] = newProduct;
            //resetting the input fields
            this.resetInputFields();
            const url = urlUpdateProduct + newProduct.id.toString(10);
            //the POST request to API
            await fetch(url, {
                method : "POST",
                body : JSON.stringify(newProduct),
                headers: {
                    'Content-Type': 'application/json'
                }
            })
            .then((res) => alert("Product Updated Successfully!"))
            .catch((err) => console.log(err));  
        }   
    },
    //the mounted function to fecth data before rendering app
    mounted : async function mounted(){
        const response  = await fetch(urlGetProduct);
        this.products = await response.json();
        for(product of this.products) {
            product.showEditForm = false;
        }
    }
});

app.mount("#app");

Designing the Component’s Template

We are now back to our index.html file.

We have created an h1 tag for displaying the heading at the top of the web page. Then we have to create a div element with id as “app”. This will be the playground of our Vue component. All the data fields declared in our component’s data option are accessible here. First, we will design a form for adding a new product. Then a section to display product details. Finally, a form for editing products. This will be very similar to the first form.

For achieving all this, Vue gives us the power of directives. Vue Directives

Directives are like instructions to Vue to do something. Vue provides us with many directives in our armoury. These directives can be used for listening to events and conditional rendering of HTML elements to the DOM.

Add Form

Let’s start with the add form. Here we will use the directives “v-on” and “v-show”. The “v-on” directive is used for listening to DOM events. This includes events like clicking, hovering, scrolling and many more. We will use this directive to listen to click events on the “Add a new product” button. On this click event, we can either perform some JavaScript logic or we can call some method of our component declared earlier. While calling the method, we can also pass arguments.

<button v-on:click=”addProductButtonPressed”> Add Product </button>

Vue also provides shortcuts for some directives. For example, “v-on” can be replaced with @ symbol.

<button @click=”addProductButtonPressed”> Add Product </button>

We will use the “v-show” directive to show the form for adding products. The “v-show” directive can be used to toggle the display of some HTML element based on some boolean condition. In the form, we will make use of the boolean variable showAddForm to decide whether to display the form or not.

Now in designing the form, we make use of the “v-model” directive. We can use this directive in the input fields of the form. This creates a two-way binding between the input fields and the data fields of our component. This way we can easily read the input values and even reset them when required.

<input type="text" placeholder="Enter name" v-model="tempName">

At the bottom of the form will be a submit button. Here also we will make use of the “v-on” directive to listen to the click event. Product Details

In a div element with class “products-box”, we will use the directive “v-for” for looping through the products array. In the loop, along with the individual product, we will also keep a count of the index.

Now the interesting part is, inside this div, all the HTML elements will be rendered one after the other for all the individual products. And for different products, we will be displaying dynamic content based on the product details.

We can use “v-bind” for binding the attributes of HTML element with dynamic content.

<img v-bind:src=”product.imageURL” alt=”product-image”>

Along with “v-for” directive we also have access to “v-if” and “v-else” directive which we can use in an if-else situation. But, it is beneficial to use “v-show”, instead of “v-if” anywhere possible because “v-show” only displays or hides the content. In case the condition is false, it does not remove any part from the DOM, it just hides it. For displaying dynamic text content, we have to wrap data fields inside double braces.

<h2 class="product_name">{{product.name}}</h2>

Edit Product

Finally, we will design the form for edit product functionality. The layout of this form will be similar to the earlier form. We will be using the same directives “v-show”, “v-on” and “v-model”. The only difference will be the logic for handling the submission of the form.

So after designing the component template, our index.html will look something like this:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Webtutsplus Market</title>
    <!-- The Vue 3.0.2 CDN -->
    <script src="https://unpkg.com/vue@3.0.2"></script>
    <!-- Bootstrap 4 CDN -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <!-- Google fonts -->
    <link rel="preconnect" href="https://fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css2?family=Courgette&display=swap" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;1,300;1,400&display=swap" rel="stylesheet">
    <!-- External Style sheet -->
    <link rel="stylesheet" href="./styles.css"></link>
</head>
<body>
    <h1 id="heading">Webtutsplus Market</h1>

    <div id="app" class="container">
        <!-- =======ADD PRODUCT=========  -->
        <button type="button" @click="addProductButtonPressed" class="btn btn-success add-btn">Add a new Product</button>
        <form v-show="showAddForm">
            <div class="form-group">
                <label>Name</label>
                <input type="text" class="form-control" placeholder="Enter name" required v-model="tempName">
            </div>
            <div class="form-group">
                <label>Description</label>
                <input type="text" class="form-control" placeholder="Enter description" required v-model="tempDescription">
            </div>
            <div class="form-group">
                <label>ImageURL</label>
                <input type="url" class="form-control" placeholder="Enter imageURL" required v-model="tempImageURL">
            </div>
            <div class="form-group">
                <label>Price</label>
                <input type="number" class="form-control" placeholder="Enter price" required v-model="tempPrice">
            </div>
            <button @click="addProduct" type="button" class="btn btn-primary">Submit</button>
        </form>
        <!-- =====DISPLAYING PRODUCTS===== -->
        <div class="products_box" v-for="(product, index) of products">
            <div class="product_item">
                <!-- =====PRODUCT DETAILS===== -->
                <div class="row">
                    <div class="col-4">
                        <img class="img-fluid" v-bind:src="product.imageURL" alt="product-image">
                    </div>
                    <div class="col-8">
                        <button @click="editButtonPressed(index)" class="btn btn-primary edit_btn">
                            <span v-if="!product.showEditForm">Edit</span>
                            <span v-else>Cancel</span>
                        </button>
                        <h2 class="product_name">{{product.name}}</h2>
                        <h3 class="product_description">{{product.description}}</h3>
                        <h3 class="product_price"><span>$</span>{{product.price}}</h3>
                        <button type="button" class="buy_btn btn btn-lg btn-outline-success">Buy Now</button>
                    </div>
                </div>
                <!-- ======EDIT FORM====== -->
                <div>
                    <form v-show="product.showEditForm">
                        <div class="form-group">
                            <label>Name</label>
                            <input type="text" class="form-control" placeholder="Enter name" required v-model="tempName">
                        </div>
                        <div class="form-group">
                            <label>Description</label>
                            <input type="text" class="form-control" placeholder="Enter description" required v-model="tempDescription">
                        </div>
                        <div class="form-group">
                            <label>ImageURL</label>
                            <input type="url" class="form-control" placeholder="Enter imageURL" required v-model="tempImageURL">
                        </div>
                        <div class="form-group">
                            <label>Price</label>
                            <input type="number" class="form-control" placeholder="Enter price" required v-model="tempPrice">
                        </div>
                        <button @click="editProduct(index)" type="button" class="btn btn-primary">Submit</button>
                    </form>
                </div>
            </div>
        </div>
    </div>

    <script src = "app.js"></script>
</body>
</html>

In this HTML file, the div element with class “product-item” encapsulates displaying the product details and its edit form. It is this div element, which is rendered for all the products.

Alt Text

CSS

Let’s also add some custom CSS :

html, body {
    font-family: 'Lato', 'Arial', sans-serif;
    font-weight: 500;
}

#heading{
    text-align: center;
    font-family: 'Courgette', cursive;
    font-size: 100;
    color: rgb(218, 216, 212);
    background-image : linear-gradient(rgba(0,0,0,0.6),rgba(0,0,0,0.6)), url("https://entrepreneurhandbook.co.uk/wp-content/uploads/2020/05/eCommerce-shopping.jpg");
    background-attachment: fixed;
    background-size: cover;
    background-repeat: no-repeat;
    padding: 180px 0;
    margin-bottom:20px;
}

img{
    border-radius : 10px;
    margin : 10px;
}

.product_item{
    background-color: rgb(246, 241, 245);
    border-radius: 10px;
    margin: 20px 0;
    padding: 10px 10px;
}

.product_name{
    font-size: 40px;
}
.product_name:hover{
    color:rgb(43, 107, 226);
    font-size: 40px;
}

.product_description{
    font-size: 20px;
}

.product_price{
    font-size: 20px;
    font-weight: 500;
    color:red;
}

.product_price span{
    font-size:15px;
}

.edit_btn{
    float : right;
    margin : 10px 10px 0 0;
}

.buy_btn{
    margin-top: 68px;
}

.btn:focus {
    outline: none;
    box-shadow: none;
}

So now we have a front-end to display all the products and their details. We have also got the functionality to add new products and edit the existing ones.

Alt Text

Why use a framework?

Front-end frameworks have made the lives of developers a lot easier. The biggest advantage of using a framework over traditional JavaScript is the link between our data and its presentation. Using frameworks, developers do not need to worry about writing the code for updating DOM when some variables change. Frameworks achieve this very easily using two-way binding. Along with easing our work, some frameworks also provide additional features like routing, which can be used to create Single Page Applications.

But why Vue.js?

Vue.js is getting backed by a lot of developers. The Vue.js community is rapidly growing. The recent updates to Vue have made it a lot more robust. Many prefer Vue.js over other frameworks because of its ease-of-use, its performance, and its compact size. The official documentation of Vue is a great resource to get a deep dive into the framework. Many also consider Vue as a combination of all good parts of React and Angular. Hence, Vue.js is worth giving a try. Follow the next tutorial in the series, where we will create categories and link products with categories.

Let's create a Vue.js E-commerce app

Resources

Creating a simple Vue.js website for our backend

Thank You for Reading!