javascriptroom guide

Hands-On Project: Building an E-Commerce Site with Vue.js

In today’s digital age, e-commerce has become a cornerstone of business, and building a responsive, user-friendly online store is a valuable skill. Vue.js, a progressive JavaScript framework, is an excellent choice for this task due to its simplicity, reactivity, and robust ecosystem (e.g., Vuex for state management, Vue Router for navigation). This hands-on guide will walk you through building a fully functional e-commerce site with Vue.js. By the end, you’ll have a site with product listings, detailed product pages, a shopping cart, and seamless navigation—all powered by Vue’s core features and best practices.

Table of Contents

  1. Prerequisites
  2. Project Setup
  3. Project Structure Overview
  4. Core Components Development
  5. State Management with Vuex
  6. Routing with Vue Router
  7. Backend Integration
  8. UI Enhancement with Vuetify
  9. Testing the Application
  10. Deployment
  11. Conclusion
  12. References

Prerequisites

Before starting, ensure you have the following tools installed:

  • Node.js (v14+ recommended) and npm (v6+) or Yarn (v1+): To run the Vue.js development server and manage dependencies.
  • Vue CLI: The official Vue.js scaffolding tool. Install it globally with:
    npm install -g @vue/cli  
    # or  
    yarn global add @vue/cli  
  • Basic knowledge of Vue.js: Familiarity with components, props, directives (e.g., v-for, v-bind), and lifecycle hooks.
  • Basic JavaScript/HTML/CSS: For handling logic, structure, and styling.

Project Setup

Let’s start by creating a new Vue.js project with Vue CLI. We’ll include essential features like Vuex (state management) and Vue Router (routing) during setup.

  1. Run the following command and follow the prompts:

    vue create ecommerce-site  
  2. Select “Manually select features” and check:

    • Babel
    • Vuex
    • Vue Router
    • CSS Pre-processors (we’ll use Sass/SCSS)
    • Linter / Formatter
  3. Choose your preferred CSS pre-processor (e.g., Sass/SCSS with dart-sass), linter (e.g., ESLint + Standard config), and set “Lint on save” for real-time feedback.

  4. Navigate into the project folder and start the development server:

    cd ecommerce-site  
    npm run serve  
    # or  
    yarn serve  
  5. Open http://localhost:8080 in your browser—you should see the default Vue welcome page.

Project Structure Overview

Familiarize yourself with the generated project structure:

ecommerce-site/  
├── public/          # Static assets (index.html, favicon)  
├── src/  
│   ├── assets/      # Images, fonts, global CSS  
│   ├── components/  # Reusable UI components (e.g., ProductCard)  
│   ├── views/       # Page-level components (e.g., ProductList, Cart)  
│   ├── router/      # Vue Router configuration  
│   ├── store/       # Vuex state management  
│   ├── services/    # API calls (e.g., product fetching)  
│   ├── App.vue      # Root component  
│   └── main.js      # Entry point  
├── package.json     # Dependencies and scripts  
└── ...  

Core Components Development

4.1 Product Listing Page

The product listing page displays all available products. We’ll create a ProductList view and a reusable ProductCard component.

Step 1: Create the ProductCard Component

Create src/components/ProductCard.vue:

<template>  
  <div class="product-card">  
    <img :src="product.image" :alt="product.name" class="product-image">  
    <h3 class="product-name">{{ product.name }}</h3>  
    <p class="product-price">${{ product.price.toFixed(2) }}</p>  
    <button @click="addToCart" class="add-to-cart-btn">Add to Cart</button>  
  </div>  
</template>  

<script>  
export default {  
  props: {  
    product: {  
      type: Object,  
      required: true,  
      // Define expected product properties  
      properties: {  
        id: Number,  
        name: String,  
        price: Number,  
        image: String  
      }  
    }  
  },  
  methods: {  
    addToCart() {  
      // Dispatch Vuex action to add product to cart (we’ll implement this later)  
      this.$store.dispatch('addToCart', this.product);  
    }  
  }  
};  
</script>  

<style scoped lang="scss">  
.product-card {  
  border: 1px solid #e0e0e0;  
  border-radius: 8px;  
  padding: 16px;  
  max-width: 250px;  
  text-align: center;  

  .product-image {  
    width: 100%;  
    height: 200px;  
    object-fit: cover;  
    border-radius: 4px;  
  }  

  .product-name {  
    font-size: 1.1rem;  
    margin: 12px 0;  
  }  

  .product-price {  
    font-weight: bold;  
    color: #e53935;  
    margin: 8px 0;  
  }  

  .add-to-cart-btn {  
    background: #4caf50;  
    color: white;  
    border: none;  
    padding: 8px 16px;  
    border-radius: 4px;  
    cursor: pointer;  
    transition: background 0.3s;  

    &:hover {  
      background: #388e3c;  
    }  
  }  
}  
</style>  

Step 2: Create the ProductList View

Create src/views/ProductList.vue to display multiple ProductCard components:

<template>  
  <div class="product-list">  
    <h2>Our Products</h2>  
    <div class="product-grid">  
      <ProductCard  
        v-for="product in products"  
        :key="product.id"  
        :product="product"  
      />  
    </div>  
  </div>  
</template>  

<script>  
import ProductCard from '@/components/ProductCard.vue';  
import { fetchProducts } from '@/services/productService'; // We’ll create this service next  

export default {  
  components: { ProductCard },  
  data() {  
    return {  
      products: []  
    };  
  },  
  async mounted() {  
    // Fetch products from API on component mount  
    this.products = await fetchProducts();  
  }  
};  
</script>  

<style scoped lang="scss">  
.product-list {  
  padding: 24px;  

  h2 {  
    text-align: center;  
    margin-bottom: 32px;  
    font-size: 2rem;  
  }  

  .product-grid {  
    display: grid;  
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));  
    gap: 24px;  
    justify-items: center;  
  }  
}  
</style>  

4.2 Product Detail Page

Create a ProductDetail view to display a single product’s details (e.g., description, larger image).

<template>  
  <div class="product-detail">  
    <div class="product-image-container">  
      <img :src="product.image" :alt="product.name" class="product-image">  
    </div>  
    <div class="product-info">  
      <h1>{{ product.name }}</h1>  
      <p class="price">${{ product.price.toFixed(2) }}</p>  
      <p class="description">{{ product.description }}</p>  
      <div class="quantity-selector">  
        <label>Quantity:</label>  
        <input type="number" v-model="quantity" min="1" max="10">  
      </div>  
      <button @click="addToCart" class="add-to-cart-btn">Add to Cart</button>  
    </div>  
  </div>  
</template>  

<script>  
import { fetchProductById } from '@/services/productService';  

export default {  
  data() {  
    return {  
      product: null,  
      quantity: 1  
    };  
  },  
  async mounted() {  
    // Get product ID from route params  
    const productId = this.$route.params.id;  
    this.product = await fetchProductById(productId);  
  },  
  methods: {  
    addToCart() {  
      // Add product with selected quantity to cart  
      this.$store.dispatch('addToCart', { ...this.product, quantity: this.quantity });  
      this.$router.push('/cart'); // Redirect to cart after adding  
    }  
  }  
};  
</script>  

<style scoped lang="scss">  
/* Add styling for layout, image, and buttons */  
</style>  

4.3 Shopping Cart

Create a Cart view to display items in the cart, quantities, and total price.

<template>  
  <div class="cart">  
    <h2>Your Cart</h2>  
    <div v-if="cartItems.length === 0" class="empty-cart">  
      <p>Your cart is empty!</p>  
      <router-link to="/">Continue Shopping</router-link>  
    </div>  
    <div v-else>  
      <div class="cart-items">  
        <div v-for="item in cartItems" :key="item.id" class="cart-item">  
          <img :src="item.image" :alt="item.name" class="item-image">  
          <div class="item-details">  
            <h3>{{ item.name }}</h3>  
            <p>Price: ${{ item.price.toFixed(2) }}</p>  
            <div class="quantity-controls">  
              <button @click="updateQuantity(item.id, item.quantity - 1)" :disabled="item.quantity <= 1">-</button>  
              <span>{{ item.quantity }}</span>  
              <button @click="updateQuantity(item.id, item.quantity + 1)" :disabled="item.quantity >= 10">+</button>  
            </div>  
            <button @click="removeItem(item.id)" class="remove-btn">Remove</button>  
          </div>  
          <p class="item-total">${{ (item.price * item.quantity).toFixed(2) }}</p>  
        </div>  
      </div>  
      <div class="cart-summary">  
        <p>Total: ${{ cartTotal.toFixed(2) }}</p>  
        <button class="checkout-btn">Proceed to Checkout</button>  
      </div>  
    </div>  
  </div>  
</template>  

<script>  
import { mapGetters, mapActions } from 'vuex';  

export default {  
  computed: {  
    ...mapGetters(['cartItems', 'cartTotal'])  
  },  
  methods: {  
    ...mapActions(['updateQuantity', 'removeFromCart']),  
    removeItem(id) {  
      this.removeFromCart(id);  
    }  
  }  
};  
</script>  

<style scoped lang="scss">  
/* Add styling for cart layout, items, and buttons */  
</style>  

State Management with Vuex

Use Vuex to manage the cart state globally (shared across components).

Step 1: Define the Store

Update src/store/index.js:

import Vue from 'vue';  
import Vuex from 'vuex';  

Vue.use(Vuex);  

export default new Vuex.Store({  
  state: {  
    cartItems: [] // Array of cart items: { id, name, price, image, quantity }  
  },  
  mutations: {  
    ADD_TO_CART(state, product) {  
      // Check if product already exists in cart  
      const existingItem = state.cartItems.find(item => item.id === product.id);  
      if (existingItem) {  
        existingItem.quantity += product.quantity || 1;  
      } else {  
        state.cartItems.push({ ...product, quantity: product.quantity || 1 });  
      }  
    },  
    UPDATE_QUANTITY(state, { id, quantity }) {  
      const item = state.cartItems.find(item => item.id === id);  
      if (item) item.quantity = quantity;  
    },  
    REMOVE_FROM_CART(state, id) {  
      state.cartItems = state.cartItems.filter(item => item.id !== id);  
    }  
  },  
  actions: {  
    addToCart({ commit }, product) {  
      commit('ADD_TO_CART', product);  
    },  
    updateQuantity({ commit }, { id, quantity }) {  
      commit('UPDATE_QUANTITY', { id, quantity });  
    },  
    removeFromCart({ commit }, id) {  
      commit('REMOVE_FROM_CART', id);  
    }  
  },  
  getters: {  
    cartItems: state => state.cartItems,  
    cartTotal: state => state.cartItems.reduce((total, item) => total + (item.price * item.quantity), 0),  
    itemCount: state => state.cartItems.reduce((count, item) => count + item.quantity, 0)  
  }  
});  

Routing with Vue Router

Define routes in src/router/index.js to navigate between pages.

import Vue from 'vue';  
import VueRouter from 'vue-router';  
import ProductList from '../views/ProductList.vue';  
import ProductDetail from '../views/ProductDetail.vue';  
import Cart from '../views/Cart.vue';  

Vue.use(VueRouter);  

const routes = [  
  {  
    path: '/',  
    name: 'ProductList',  
    component: ProductList  
  },  
  {  
    path: '/product/:id',  
    name: 'ProductDetail',  
    component: ProductDetail,  
    props: true // Pass route params as props to ProductDetail  
  },  
  {  
    path: '/cart',  
    name: 'Cart',  
    component: Cart  
  }  
];  

const router = new VueRouter({  
  mode: 'history',  
  base: process.env.BASE_URL,  
  routes  
});  

export default router;  

Update src/App.vue to include navigation links and a cart icon with item count:

<template>  
  <div id="app">  
    <nav class="navbar">  
      <router-link to="/" class="logo">E-Commerce Store</router-link>  
      <div class="nav-links">  
        <router-link to="/">Home</router-link>  
        <router-link to="/cart" class="cart-link">  
          Cart ({{ itemCount }})  
        </router-link>  
      </div>  
    </nav>  
    <router-view />  
  </div>  
</template>  

<script>  
import { mapGetters } from 'vuex';  

export default {  
  computed: {  
    ...mapGetters(['itemCount'])  
  }  
};  
</script>  

Backend Integration

For this project, we’ll use a mock API (e.g., JSONPlaceholder or a local Express server) to fetch product data.

Step 1: Create a Product Service

Create src/services/productService.js to handle API calls with Axios:

import axios from 'axios';  

// Use a mock API or local server URL  
const API_URL = 'https://jsonplaceholder.typicode.com';  

// Fetch all products  
export const fetchProducts = async () => {  
  try {  
    const response = await axios.get(`${API_URL}/products`);  
    return response.data;  
  } catch (error) {  
    console.error('Error fetching products:', error);  
    return [];  
  }  
};  

// Fetch product by ID  
export const fetchProductById = async (id) => {  
  try {  
    const response = await axios.get(`${API_URL}/products/${id}`);  
    return response.data;  
  } catch (error) {  
    console.error(`Error fetching product ${id}:`, error);  
    return null;  
  }  
};  

Install Axios first:

npm install axios  

UI Enhancement with Vuetify

Vuetify is a Material Design component library for Vue.js that simplifies UI development. Install it with:

vue add vuetify  

Update components to use Vuetify’s pre-built components (e.g., <v-card>, <v-btn>, <v-img>) for a polished look. For example, refactor ProductCard.vue with Vuetify:

<template>  
  <v-card class="product-card" max-width="250">  
    <v-img :src="product.image" :alt="product.name" height="200"></v-img>  
    <v-card-title>{{ product.name }}</v-card-title>  
    <v-card-text>  
      <p class="product-price">${{ product.price.toFixed(2) }}</p>  
    </v-card-text>  
    <v-card-actions>  
      <v-btn color="success" @click="addToCart" block>Add to Cart</v-btn>  
    </v-card-actions>  
  </v-card>  
</template>  

Testing the Application

Test key features manually:

  1. Product Listing: Verify products load and display correctly.
  2. Add to Cart: Click “Add to Cart” and check if the cart icon updates.
  3. Product Detail: Navigate to a product detail page and add with a custom quantity.
  4. Cart Management: Update quantities, remove items, and verify the total price.

For automated testing, use Vue Test Utils to write unit tests for components like ProductCard.

Deployment

Deploy your e-commerce site to a platform like Netlify or Vercel:

  1. Build the project:

    npm run build  

    This generates a dist/ folder with optimized assets.

  2. Netlify: Drag-and-drop the dist/ folder into the Netlify dashboard or connect your GitHub repo for CI/CD.

Conclusion

In this project, you built a functional e-commerce site with Vue.js, including product listing, details, cart management, and routing. Key takeaways:

  • Vue.js’s component-based architecture simplifies UI development.
  • Vuex manages global state (e.g., cart items) across components.
  • Vue Router enables seamless navigation between pages.
  • Vuetify accelerates UI design with Material Design components.

Extend the project by adding user authentication (Firebase), payment integration (Stripe), or product reviews!

References