The author selected the Open Source Initiative to receive a donation as part of the Write for DOnations program.
Vue.js is a performant and progressive Javascript framework. It is a popular framework on GitHub and has an active and helpful community.
In order to show the capabilities of the Vue web framework, this tutorial will lead you through building the shopping cart of an e-commerce app. This app will store product information and hold the products that the customer wants to buy for checkout later. To store the information, you will explore a widely used state management library for Vue.js: Vuex. This will allow the shopping cart application to persist data to a server. You will also handle asynchronous task management using Vuex.
Once you finish the tutorial, you will have a functioning shopping cart application like the following:
You will need a development environment running Node.js; this tutorial was tested on Node.js version 10.22.0 and npm version 6.14.6. To install this on macOS or Ubuntu 18.04, follow the steps in How to Install Node.js and Create a Local Development Environment on macOS or the Installing Using a PPA section of How To Install Node.js on Ubuntu 18.04.
You will also need a basic knowledge of JavaScript, HTML, and CSS, which you can find in our How To Build a Website With HTML series, How To Build a Website With CSS series, and in How To Code in JavaScript.
As of version 4.5.0, Vue CLI now provides a built-in option to choose the Vue 3 preset when creating a new project. The latest version of Vue CLI allows you to use Vue 3 out of the box and to update your existing Vue 2 project to Vue 3. In this step, you will use the Vue CLI to make your project, then install the front-end dependencies.
First, install the latest version of Vue CLI by executing the following command from the terminal:
- npm install -g @vue/cli
This will install Vue CLI globally on your system.
Note: On some systems, installing an npm package globally can result in a permission error, which will interrupt the installation. Since it is a security best practice to avoid using sudo
with npm install
, you can instead resolve this by changing npm’s default directory. If you encounter an EACCES
error, follow the instructions at the official npm documentation.
Check you have the right version with this command:
- vue --version
You will get output like the following:
Output@vue/cli 4.5.10
Note: If you already have the older version of Vue CLI installed globally, execute the following command from the terminal to upgrade:
- npm update -g @vue/cli
Now, you can create a new project:
- vue create vuex-shopping-cart
This uses the Vue CLI command vue create
to make a project named vuex-shopping-cart
. For more information on the Vue CLI, check out How To Generate a Vue.js Single Page App With the Vue CLI.
Next, you will receive the following prompt:
OutputVue CLI v4.5.10
? Please pick a preset: (Use arrow keys)
❯ Default ([Vue 2] babel, eslint)
Default (Vue 3 Preview) ([Vue 3] babel, eslint)
Manually select features
Choose the Manually select features
option from this list.
Next, you will encounter the following prompt to customize your Vue app:
Output...
◉ Choose Vue version
◯ Babel
◯ TypeScript
◯ Progressive Web App (PWA) Support
◉ Router
◉ Vuex
◯ CSS Pre-processors
◯ Linter / Formatter
❯◯ Unit Testing
◯ E2E Testing
From this list, select Choose Vue version
, Router
, and Vuex
. This will allow you to choose your version of Vue and use Vuex and Vue Router.
Next, choose 3.x (Preview)
for your version of Vue, answer no (N
) to history mode
, and select the option to have your configurations In dedicated config file
. Finally, answer N
to avoid saving the setup for a future project.
At this point, Vue will create your application.
After the project creation, move into the folder using the command:
- cd vuex-shopping-cart
To start, you’ll install Bulma, a free, open-source CSS framework based on Flexbox. Add Bulma to your project by running the following command:
- npm install bulma
To use Bulma CSS in your project, open up your app’s entry point, the main.js
file:
- nano src/main.js
Then add the following highlighted import line:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './../node_modules/bulma/css/bulma.css'
createApp(App).use(store).use(router).mount('#app')
Save and close the file.
In this app, you’ll use the Axios module to make requests to your server. Add the Axios module by running the following command:
- npm install axios
Now, run the app to make sure it is working:
- npm run serve
Navigate to http://localhost:8080
in your browser of choice. You will find the Vue app welcome page:
Once you have confirmed that Vue is working, stop your server with CTRL+C
.
In this step, you globally installed Vue CLI in your computer, created a Vue project, installed the required npm packages Axios and Bulma, and imported Bulma to the project in the main.js
file. Next, you will set up a back-end API to store data for your app.
In this step, you will create a separate backend to work with your Vue project. This will be in a different project folder from your front-end Vue application.
First, move out of your Vue directory:
- cd ..
Make a separate directory named cart-backend
:
- mkdir cart-backend
Once you have your back-end folder, make it your working directory:
- cd cart-backend
You will get started by initializing the project with the necessary file. Create the file structure of your app with the following commands:
- touch server.js
- touch server-cart-data.json
- touch server-product-data.json
You use the touch
command here to create empty files. The server.js
file will hold your Node.js server, and the JSON will hold data for the shop’s products and the user’s shopping cart.
Now run the following command to create a package.json
file:
- npm init
For more information on npm and Node, check out our How To Code in Node.js series.
Install these back-end dependencies into your Node project:
- npm install concurrently express body-parser
Express is a Node framework for web applications, which will provide useful abstractions for handling API requests. Concurrently will be used to run the Express back-end server and the Vue.js development server simulteneously. Finally, body-parser
is an Express middleware that will parse requests to your API.
Next, open a server.js
file in the root of your application:
- nano server.js
Then add the following code:
const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const path = require('path');
const app = express();
const PRODUCT_DATA_FILE = path.join(__dirname, 'server-product-data.json');
const CART_DATA_FILE = path.join(__dirname, 'server-cart-data.json');
app.set('port', (process.env.PORT || 3000));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use((req, res, next) => {
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
next();
});
app.listen(app.get('port'), () => {
console.log(`Find the server at: http://localhost:${app.get('port')}/`);
});
This snippet first adds the Node modules to your backend, including the fs
module to write to your filesystem and the path
module to make defining filepaths easier. You then initialize the Express app
and save references to your JSON files as PRODUCT_DATA_FILE
and CART_DATA_FILE
. These will be used as data repositories. Finally, you created an Express server, set the port, created a middleware to set the response headers, and set the server to listen on your port. For more information on Express, see the official Express documentation.
The setHeader
method sets the header of the HTTP responses. In this case, you are using Cache-Control
to direct the caching of your app. For more information on this, check out the Mozilla Developer Network article on Cache-Control.
Next, you will create an API endpoint that your frontend will query to add an item to the shopping cart. To do this, you will use app.post
to listen for an HTTP POST
request.
Add the following code to server.js
just after the last app.use()
middleware:
...
app.use((req, res, next) => {
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
next();
});
app.post('/cart', (req, res) => {
fs.readFile(CART_DATA_FILE, (err, data) => {
const cartProducts = JSON.parse(data);
const newCartProduct = {
id: req.body.id,
title: req.body.title,
description: req.body.description,
price: req.body.price,
image_tag: req.body.image_tag,
quantity: 1
};
let cartProductExists = false;
cartProducts.map((cartProduct) => {
if (cartProduct.id === newCartProduct.id) {
cartProduct.quantity++;
cartProductExists = true;
}
});
if (!cartProductExists) cartProducts.push(newCartProduct);
fs.writeFile(CART_DATA_FILE, JSON.stringify(cartProducts, null, 4), () => {
res.setHeader('Cache-Control', 'no-cache');
res.json(cartProducts);
});
});
});
app.listen(app.get('port'), () => {
console.log(`Find the server at: http://localhost:${app.get('port')}/`);
});
This code receives the request object containing the cart items from the frontend and stores them in the server-cart-data.json
file in the root of your project. Products here are JavaScript objects with id
, title
, description
, price
, image_tag
, and quantity
properties. The code also checks if the cart already exists to ensure that requests for a repeated product only increase the quantity
.
Now, add code to create an API endpoint to remove an item from the shopping cart. This time, you will use app.delete
to listen for an HTTP DELETE
request.
Add the following code to server.js
just after the previous endpoint:
...
fs.writeFile(CART_DATA_FILE, JSON.stringify(cartProducts, null, 4), () => {
res.setHeader('Cache-Control', 'no-cache');
res.json(cartProducts);
});
});
});
app.delete('/cart/delete', (req, res) => {
fs.readFile(CART_DATA_FILE, (err, data) => {
let cartProducts = JSON.parse(data);
cartProducts.map((cartProduct) => {
if (cartProduct.id === req.body.id && cartProduct.quantity > 1) {
cartProduct.quantity--;
} else if (cartProduct.id === req.body.id && cartProduct.quantity === 1) {
const cartIndexToRemove = cartProducts.findIndex(cartProduct => cartProduct.id === req.body.id);
cartProducts.splice(cartIndexToRemove, 1);
}
});
fs.writeFile(CART_DATA_FILE, JSON.stringify(cartProducts, null, 4), () => {
res.setHeader('Cache-Control', 'no-cache');
res.json(cartProducts);
});
});
});
app.listen(app.get('port'), () => {
console.log(`Find the server at: http://localhost:${app.get('port')}/`); // eslint-disable-line no-console
});
This code receives the request object containing the item to be removed from the cart and checks the server-cart-data.json
file for this item via its id
. If it exists and the quantity is greater than one, then the quantity of the item in the cart is deducted. Otherwise, if the item’s quantity is less than 1, it will be removed from the cart and the remaining items will be stored in the server-cart-data.json
file.
To give your user additional functionality, you can now create an API endpoint to remove all items from the shopping cart. This will also listen for a DELETE
request.
Add the following highlighted code to server.js
after the previous endpoint:
...
fs.writeFile(CART_DATA_FILE, JSON.stringify(cartProducts, null, 4), () => {
res.setHeader('Cache-Control', 'no-cache');
res.json(cartProducts);
});
});
});
app.delete('/cart/delete/all', (req, res) => {
fs.readFile(CART_DATA_FILE, () => {
let emptyCart = [];
fs.writeFile(CART_DATA_FILE, JSON.stringify(emptyCart, null, 4), () => {
res.json(emptyCart);
});
});
});
app.listen(app.get('port'), () => {
console.log(`Find the server at: http://localhost:${app.get('port')}/`); // eslint-disable-line no-console
});
This code is responsible for removing all the items from the cart by returning an empty array.
Next, you will create an API endpoint to retrieve all the products from the product storage. This will use app.get
to listen for a GET
request.
Add the following code to server.js
after the previous endpoint:
...
app.delete('/cart/delete/all', (req, res) => {
fs.readFile(CART_DATA_FILE, () => {
let emptyCart = [];
fs.writeFile(CART_DATA_FILE, JSON.stringify(emptyCart, null, 4), () => {
res.json(emptyCart);
});
});
});
app.get('/products', (req, res) => {
fs.readFile(PRODUCT_DATA_FILE, (err, data) => {
res.setHeader('Cache-Control', 'no-cache');
res.json(JSON.parse(data));
});
});
...
This code uses the file system’s native readFile
method to fetch all the data in the server-product-data.json
file and returns them in JSON format.
Finally, you will create an API endpoint to retrieve all the items from the cart storage:
...
app.get('/products', (req, res) => {
fs.readFile(PRODUCT_DATA_FILE, (err, data) => {
res.setHeader('Cache-Control', 'no-cache');
res.json(JSON.parse(data));
});
});
app.get('/cart', (req, res) => {
fs.readFile(CART_DATA_FILE, (err, data) => {
res.setHeader('Cache-Control', 'no-cache');
res.json(JSON.parse(data));
});
});
...
Similarly, this code uses the file system’s native readFile
method to fetch all the data in the server-cart-data.json
file and returns them in JSON format.
Save and close the server.js
file.
Next, you will add some mock data to your JSON files for testing purposes.
Open up the server-cart-data.json
file you created earlier:
- nano server-cart-data.json
Add the following array of product objects:
[
{
"id": 2,
"title": "MIKANO Engine",
"description": "Lorem ipsum dolor sit amet, consectetur dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis! ",
"price": 650.9,
"image_tag": "diesel-engine.png",
"quantity": 1
},
{
"id": 3,
"title": "SEFANG Engine",
"description": "Lorem ipsum dolor sit amet, consectetur dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis!",
"price": 619.9,
"image_tag": "sefang-engine.png",
"quantity": 1
}
]
This shows two engines that will start out in the user’s shopping cart.
Save and close the file.
Now open the server-product-data.json
file:
- nano server-product-data.json
Add the following data in server-product-data.json
file:
[
{
"id": 1,
"title": "CAT Engine",
"description": "Lorem ipsum dolor sit amet, consectetur dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis!",
"product_type": "power set/diesel engine",
"image_tag": "CAT-engine.png",
"created_at": 2020,
"owner": "Colton",
"owner_photo": "image-colton.jpg",
"email": "colt@gmail.com",
"price": 719.9
},
{
"id": 2,
"title": "MIKANO Engine",
"description": "Lorem ipsum dolor sit amet, consectetur dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis! ",
"product_type": "power set/diesel engine",
"image_tag": "diesel-engine.png",
"created_at": 2020,
"owner": "Colton",
"owner_photo": "image-colton.jpg",
"email": "colt@gmail.com",
"price": 650.9
},
{
"id": 3,
"title": "SEFANG Engine",
"description": "Lorem ipsum dolor sit amet, consectetur dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis!",
"product_type": "power set/diesel engine",
"image_tag": "sefang-engine.png",
"created_at": 2017,
"owner": "Anne",
"owner_photo": "image-anne.jpg",
"email": "anne@gmail.com",
"price": 619.9
},
{
"id": 4,
"title": "CAT Engine",
"description": "Lorem ipsum dolor sit amet, consectetur dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis!",
"product_type": "power set/diesel engine",
"image_tag": "lawn-mower.png",
"created_at": 2017,
"owner": "Irene",
"owner_photo": "image-irene.jpg",
"email": "irene@gmail.com",
"price": 319.9
}
]
This will hold all the possible products that the user can put in their cart.
Save and close the file.
Finally, execute this command to run the server:
- node server
You will receive something like this on your terminal:
OutputFind the server at: http://localhost:3000/
Leave this server running in this window.
Finally, you will set up a proxy server in your Vue app. This will enable the connection between the frontend and backend.
Go to the root directory of your Vue app:
- cd ../vuex-shopping-cart
In the terminal, run this command to create a Vue configuration file:
- nano vue.config.js
Then, add this code:
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000/',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}
This will send requests from your frontend to your back-end server at http://localhost:3000/
. For more information on proxy configuration, review the Vue devServer.proxy
documentation.
Save and close the file.
In this step, you wrote server-side code that will handle API endpoints for your shopping cart. You started by creating the file structure and ended with adding necessary code in the server.js
file and data in your JSON files. Next, you will set up the state storage for your frontend.
In Vuex, the store is where the state of the application is kept. The application state can only be updated by dispatching actions within a component that will then trigger mutations in the store. The Vuex store is made up of the state, mutations, actions, and getters.
In this step, you’re going to build each of these pieces, after which you will couple everything together into a Vuex store.
Now you will create a place to store state for your application.
The store
folder in the root directory src
of your project is automatically created at the time of the project setup. Locate the store
folder in the src
directory of your project then create a new folder named modules
:
- mkdir src/store/modules
Inside this folder, create the product
and cart
folders:
- mkdir src/store/modules/product
- mkdir src/store/modules/cart
These will hold all the state files for your product inventory and your user’s cart. You will build these two files up at the same time, each open in a separate terminal. This way, you will be able to compare your mutations, getters, and actions side-by-side.
Finally, open an index.js
file in the product
folder:
- nano src/store/modules/product/index.js
Add the following code to create a state object containing your productItems
:
import axios from 'axios';
const state = {
productItems: []
}
Save the file and keep it open.
Similarly, in a new terminal, add an index.js
file to the cart
directory with the following:
- nano src/store/modules/cart/index.js
Then add code for the cartItems
:
import axios from 'axios';
const state = {
cartItems: []
}
Save this file, but keep it open.
In these code snippets, you imported the Axios module and set the state. The state
is a store object that holds the application-level data that needs to be shared between components.
Now that you’ve set the states, head over to mutations.
Mutations are methods that modify the store state. They usually consist of a string type and a handler that accepts the state and payload as parameters.
You will now create all the mutations for your application.
Add the following code in the product/index.js
file just after the state
section:
...
const mutations = {
UPDATE_PRODUCT_ITEMS (state, payload) {
state.productItems = payload;
}
}
This creates a mutations
object that holds an UPDATE_PRODUCT_ITEMS
method that sets the productItems
array to the payload
value.
Similarly, add the following code in the cart/index.js
file just after the state section:
...
const mutations = {
UPDATE_CART_ITEMS (state, payload) {
state.cartItems = payload;
}
}
This creates a similar UPDATE_CART_ITEMS
for your user’s shopping cart. Note that this follows the Flux architecture style of making references to mutations in capital letters.
Actions are methods that will handle mutations, so that mutations are insulated from the rest of your application code.
In product/index.js
, create an actions
object with all the actions for your application:
...
const actions = {
getProductItems ({ commit }) {
axios.get(`/api/products`).then((response) => {
commit('UPDATE_PRODUCT_ITEMS', response.data)
});
}
}
Here the getProductItems
method sends an asynchronous GET
request to the server using the Axios package that you installed earlier. When the request is successful, the UPDATE_PRODUCT_ITEMS
mutation is called with the response data as the payload.
Next, add the following actions
object to cart/index.js
:
...
const actions = {
getCartItems ({ commit }) {
axios.get('/api/cart').then((response) => {
commit('UPDATE_CART_ITEMS', response.data)
});
},
addCartItem ({ commit }, cartItem) {
axios.post('/api/cart', cartItem).then((response) => {
commit('UPDATE_CART_ITEMS', response.data)
});
},
removeCartItem ({ commit }, cartItem) {
axios.delete('/api/cart/delete', cartItem).then((response) => {
commit('UPDATE_CART_ITEMS', response.data)
});
},
removeAllCartItems ({ commit }) {
axios.delete('/api/cart/delete/all').then((response) => {
commit('UPDATE_CART_ITEMS', response.data)
});
}
}
In this file, you create the getCartItems
method, which sends an asynchronous GET
request to the server. When the request is successful, the UPDATE_CART_ITEMS
mutation is called with the response data as the payload. The same happens with the removeAllCartItems
method, although it makes a DELETE
request to the server. The removeCartItem
and addCartItem
methods receives the cartItem
object as a parameter for making a DELETE
or POST
request. After a successful request, the UPDATE_CART_ITEMS
mutation is called with the response data as the payload.
You used ES6 destructuring to decouple the commit
method from the Vuex context
object. This is similar to using context.commit
.
Getters are to an application store what computed properties are to a component. They return computed information from store state methods that involve receiving computed state data.
Next, create a getters
object to get all the information for the product
module:
...
const getters = {
productItems: state => state.productItems,
productItemById: (state) => (id) => {
return state.productItems.find(productItem => productItem.id === id)
}
}
Here, you made a method productItems
that returns the list of product items in the state, followed by productItemById
, a higher order function that returns a single product by its id
.
Next, create a getters
object in cart/index.js
:
...
const getters = {
cartItems: state => state.cartItems,
cartTotal: state => {
return state.cartItems.reduce((acc, cartItem) => {
return (cartItem.quantity * cartItem.price) + acc;
}, 0).toFixed(2);
},
cartQuantity: state => {
return state.cartItems.reduce((acc, cartItem) => {
return cartItem.quantity + acc;
}, 0);
}
}
In this snippet, you made the cartItems
method, which returns the list of cart items in the state, followed by cartTotal
, which returns the computed value of the total amount of cart items available for checkout. Finally, you made the cartQuantity
method, which retuns the quantity of items in the cart.
The final part of the product
and cart
modules will export the state
, mutations
, actions
, and getters
objects so that other parts of the application can access them.
In product/index.js
, add the following code at the end of the file:
...
const productModule = {
state,
mutations,
actions,
getters
}
export default productModule;
This collects all your state objects into the productModule
object, then exports it as a module.
Save product/index.js
and close the file.
Next, add similar code to cart/index.js
:
...
const cartModule = {
state,
mutations,
actions,
getters
}
export default cartModule;
This exports the module as cartModule
.
With the state, mutations, actions, and getters all set up, the final part of integrating Vuex into your application is creating the store. Here you will harness the Vuex modules to split your application store into two manageable fragments.
To create your store, open up the index.js
file in your store
folder:
- nano src/store/index.js
Add the following highlighted lines:
import { createStore } from 'vuex'
import product from'./modules/product';
import cart from './modules/cart';
export default createStore({
modules: {
product,
cart
}
})
Save the file, then exit the text editor.
You have now created the methods needed for state management and have created the store for your shopping cart. Next you will create user interface (UI) components to consume the data.
Now that you have the store for your shopping cart set up, you can move onto making the components for the user interface (UI). This will include making some changes to the router and making front-end components for your navigation bar and list and item views of your products and your cart.
First, you will update your vue-router
setup. Remember that when you used the Vue CLI tool to scaffold your application, you chose the router option, which allowed Vue to automatically set up the router for you. Now you can re-configure the router to provide paths for Cart_List.vue
and Product_List.vue
, which are Vue components you will make later.
Open up the router file with the following command:
- nano vuex-shopping-cart/src/router/index.js
Add the following highlighted lines:
import { createRouter, createWebHashHistory } from 'vue-router'
import CartList from '../components/cart/Cart_List.vue';
import ProductList from '../components/product/Product_List.vue';
const routes = [
{
path: '/inventory',
component: ProductList
},
{
path: '/cart',
component: CartList
},
{
path: '/',
redirect: '/inventory'
},
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
This creates the /inventory
route for your products and the /cart
route for the items in your cart. It also redirects your root path /
to the product view.
Once you have added this code, save and close the file.
Now you can set up your UI component directories. Run this command on your terminal to move to the component’s directory:
- cd src/components
Run this command to create three new sub-folders under the component’s directory:
- mkdir core cart product
core
will hold essential parts of your application, such as the navigation bar. cart
and product
will hold the item and list views of the shopping cart and the total inventory.
Under the core
directory, create the Navbar.vue
file by running this command:
- touch core/Navbar.vue
Under the cart
directory, create the files Cart_List_Item.vue
and Cart_List.vue
:
- touch cart/Cart_List_Item.vue cart/Cart_List.vue
Finally, under the product
directory, create these two files:
- touch product/Product_List_Item.vue product/Product_List.vue
Now that the file structure has been outlined, you can move on to creating the individual components of your front-end app.
Navbar
ComponentIn the navbar, the cart navigation link will display the quantity of items in your cart. You will use the Vuex mapGetters
helper method to directly map store getters with component computed properties, allowing your app to get this data from the store’s getters to the Navbar
component.
Open the navbar file:
- nano core/Navbar.vue
Replace the code with the following:
<template>
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a
role="button"
class="navbar-burger burger"
aria-label="menu"
aria-expanded="false"
data-target="navbarBasicExample"
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-end">
<div class="navbar-item">
<div class="buttons">
<router-link to="/inventory" class="button is-primary">
<strong> Inventory</strong>
</router-link>
<router-link to="/cart" class="button is-warning"> <p>
Total cart items:
<span> {{cartQuantity}}</span> </p>
</router-link>
</div>
</div>
</div>
</div>
</nav>
</template>
<script>
import {mapGetters} from "vuex"
export default {
name: "Navbar",
computed: {
...mapGetters([
'cartQuantity'
])
},
created() {
this.$store.dispatch("getCartItems");
}
}
</script>
As a Vue component, this file starts out with a template
element, which holds the HTML for the component. This snippet includes multiple navbar
classes that use pre-made styles from the Bulma CSS framework. For more information, check out the Bulma documentation.
This also uses the router-link
elements to connect the app to your products and cart, and uses cartQuantity
as a computed property to dynamically keep track of the number of items in your cart.
The JavaScript is held in the script
element, which also handles state management and exports the component. The getCartItems
action gets dispatched when the navbar component is created, updating the store state with all the cart items from the response data received from the server. After this, the store getters recompute their return values and the cartQuantity
gets rendered in the template. Without dispatching the getCartItems
action on the created life cycle hook, the value of cartQuantity
will be 0 until the store state is modified.
Save and close the file.
Product_List
ComponentThis component is the parent to the Product_List_Item
component. It will be responsible for passing down the product items as props to the Product_List_Item
(child) component.
First, open up the file:
- nano product/Product_List.vue
Update Product_List.vue
as follows:
<template>
<div class="container is-fluid">
<div class="tile is-ancestor">
<div class="tile is-parent" v-for="productItem in productItems" :key="productItem.id">
<ProductListItem :productItem="productItem"/>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import Product_List_Item from './Product_List_Item'
export default {
name: "ProductList",
components: {
ProductListItem:Product_List_Item
},
computed: {
...mapGetters([
'productItems'
])
},
created() {
this.$store.dispatch('getProductItems');
}
};
</script>
Similar to the Navbar
component logic discussed earlier, here the Vuex mapGetters
helper method directly maps store getters with component computed properties to get the productItems
data from the store. The getProductItems
action gets dispatched when the ProductList
component is created, updating the store state with all the product items from the response data received from the server. After this, the store getters re-computes their return values and the productItems
gets rendered in the template. Without dispatching the getProductItems
action on the created life cycle hook, there will be no product item displayed in the template until the store state is modified.
Product_List_Item
ComponentThis component will be the direct child component to the Product_List
component. It will receive the productItem
data as props from its parent and render them in the template.
Open Product_List_Item.vue
:
- nano product/Product_List_Item.vue
Then add the following code:
<template>
<div class="card">
<div class="card-content">
<div class="content">
<h4>{{ productItem.title }}</h4>
<a
class="button is-rounded is-pulled-left"
@click="addCartItem(productItem)"
>
<strong>Add to Cart</strong>
</a>
<br />
<p class="mt-4">
{{ productItem.description }}
</p>
</div>
<div class="media">
<div class="media-content">
<p class="title is-6">{{ productItem.owner }}</p>
<p class="subtitle is-7">{{ productItem.email }}</p>
</div>
<div class="media-right">
<a class="button is-primary is-light">
<strong>$ {{ productItem.price }}</strong>
</a>
</div>
</div>
</div>
</div>
</template>
<script>
import {mapActions} from 'vuex'
export default {
name: "ProductListItem",
props: ["productItem"],
methods: {
...mapActions(["addCartItem"]),
},
};
</script>
In addition to the mapGetters
helper function used in the previous components, Vuex also provides you with the mapActions
helper function to directly map the component method to the store’s actions. In this case, you use the mapAction
helper function to map the component method to the addCartItem
action in the store. Now you can add items to the cart.
Save and close the file.
Cart_List
ComponentThis component is responsible for displaying all the product items added to the cart and also the removal of all the items from the cart.
To create this component, first open the file:
- nano cart/Cart_List.vue
Next, update Cart_List.vue
as follows:
<template>
<div id="cart">
<div class="cart--header has-text-centered">
<i class="fa fa-2x fa-shopping-cart"></i>
</div>
<p v-if="!cartItems.length" class="cart-empty-text has-text-centered">
Add some items to the cart!
</p>
<ul>
<li class="cart-item" v-for="cartItem in cartItems" :key="cartItem.id">
<CartListItem :cartItem="cartItem"/>
</li>
<div class="notification is-success">
<button class="delete"></button>
<p>
Total Quantity:
<span class="has-text-weight-bold">{{ cartQuantity }}</span>
</p>
</div>
<br>
</ul>
<div class="buttons">
<button :disabled="!cartItems.length" class="button is-info">
Checkout (<span class="has-text-weight-bold">${{ cartTotal }}</span>)
</button>
<button class="button is-danger is-outlined" @click="removeAllCartItems">
<span>Delete All items</span>
<span class="icon is-small">
<i class="fas fa-times"></i>
</span>
</button>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
import CartListItem from "./Cart_List_Item";
export default {
name: "CartList",
components: {
CartListItem
},
computed: {
...mapGetters(["cartItems", "cartTotal", "cartQuantity"]),
},
created() {
this.$store.dispatch("getCartItems");
},
methods: {
...mapActions(["removeAllCartItems"]),
}
};
</script>
This code uses a v-if
statement in the template to conditionally render a message if the cart is empty. Otherwise, it iterates through the store of cart items and renders them to the page. You also loaded in the cartItems
, cartTotal
, and cartQuantity
getters to compute the data properties, and brought in the removeAllCartItems
action.
Save and close the file.
Cart_List_Item
ComponentThis component is the direct child component of the Cart_List
component. It receives the cartItem
data as props from its parent and renders them in the template. It is also responsible for incrementing and decrementing the quantity of items in the cart.
Open up the file:
- nano cart/Cart_List_Item.vue
Update Cart_List_Item.vue
as follows:
<template>
<div class="box">
<div class="cart-item__details">
<p class="is-inline">{{cartItem.title}}</p>
<div>
<span class="cart-item--price has-text-info has-text-weight-bold">
${{cartItem.price}} X {{cartItem.quantity}}
</span>
<span>
<i class="fa fa-arrow-circle-up cart-item__modify" @click="addCartItem(cartItem)"></i>
<i class="fa fa-arrow-circle-down cart-item__modify" @click="removeCartItem(cartItem)"></i>
</span>
</div>
</div>
</div>
</template>
<script>
import { mapActions } from 'vuex';
export default {
name: 'CartListItem',
props: ['cartItem'],
methods: {
...mapActions([
'addCartItem',
'removeCartItem'
])
}
}
</script>
Here, you are using the mapAction
helper function to map the component method to the addCartItem
and removeCartItem
actions in the store.
Save and close the file.
Lastly, you will update the App.vue
file to bring these components into your app. First, move back to the root folder of your project:
- cd ../..
Now open the file:
- nano src/App.vue
Replace the contents with the following code:
<template>
<div>
<Navbar/>
<div class="container mt-6">
<div class="columns">
<div class="column is-12 column--align-center">
<router-view></router-view>
</div>
</div>
</div>
</div>
</template>
<script>
import Navbar from './components/core/Navbar'
export default {
name: 'App',
components: {
Navbar
}
}
</script>
<style>
html,
body {
height: 100%;
background: #f2f6fa;
}
</style>
App.vue
is the root of your application defined in Vue component file format. Once you have made the changes, save and close the file.
In this step, you set up the frontend of your shopping cart app by creating components for the navigation bar, the product inventory, and the shopping cart. You also used the store actions and getters that you created in a previous step. Next, you will get your application up and running.
Now that your app is ready, you can start the development server and try out the final product.
Run the following command in the root of your front-end project:
- npm run serve
This will start a development server that allows you to view your app on http://localhost:8080
. Also, make sure that your backend is running in a separate terminal; you can do this by running the following command in your cart-backend
project:
- node server
Once your backend and your frontend are running, navigate to http://localhost:8080
in your browser. You will find your functioning shopping cart application:
In this tutorial, you built an online shopping cart app using Vue.js and Vuex for data management. These techniques can be reused to form the basis of an e-commerce shopping application. If you would like to learn more about Vue.js, check out our Vue.js topic page.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
nice videos but I just can’t follow up without the source code, please pls send me the Github link it will me catch fast with good understanding thanks
The first try did not work to me
I saw several .ts files in the tree of files … do we need them ?
it seems that App.Vue do not include other components like inventory or cart … only NAVBAR …
Hey. When I try to delete cart items one by one it doesn’t work. Do you know the solution?