E-commerce applications are one of the recent fast-growing technological ideas of the century, but this isn’t surprising as its advantages far trump its downsides. Now a store owner can put their goods online and make huge returns while also reducing the stress the buyer has to go through as opposed to traditional shopping.
But how does this apply to a software developer?
Building a robust e-commerce website as a developer shows you must have learnt and honed your skill in dealing with a large amount of data while ensuring that all use cases have been taken care of.
So, what better way to show off your proficiency than by building a mini e-commerce application?
In this tutorial, we will be discussing how to build an e-commerce application in Vue.js using some interesting tools like; Auth0, Cloudinary, Vuex and Paystack.
Tool
- Auth0 - Auth0 is going to be used to handle the application’s authentication.
- Cloudinary - This is a video and image management service that will be used for all media processes on the e-commerce website.
- Vuex - Vuex will take care of our application’s state management.
- Tachyons - This is the CSS framework to will be using for our e-commerce website.
- Paystack - Paystack will act as a payment gateway for all purchases made on the e-commerce store.
- Vue Router - Vue router will be acting as a link that connects the different Vue components to allow rendering and viewing based on the user navigation.
All these tools may seem a little bit overwhelming but they will be demystified as they are put together to build the e-commerce fashion store named Rayo E-store.
Case Study
Rayo E-store is an online e-commerce store for a Fashion retailer in Africa. For a long time, African businesses have had difficulty showing their wares online and even more problems receiving payments for purchased goods. In this tutorial, we will be building a solution to address this problem.
Here is what the final product of the solution will look like…
Prerequisites
This tutorial assumes the reader has the following:
- Node.js installed on their computer.
- Familiarity with Vue.js and Vuex.
- Vue-router
- Tachyons
- Paystack
- Auth0
- Cloudinary
NB: The best part of this is that creating an account and using Cloudinary and Auth0 in your project is completely free.
Getting Started
To build our e-commerce site, we will be using the Vue CLI to create a Vue app and Cloudinary to manage our media assets for the application. We will also be using VueX and Paystack to handle state management and payment integration, respectively.
Check out the Github repo here, if you would like to jump right into the code.
Now, let’s go ahead and begin building.
Create Vue App
Open a terminal on your computer and run the following command.
vue create vue-ecommerce
cd vue-ecommerce
npm run serve
Running npm run serve
starts the project on the development server at localhost:8080
in your browser.
Styling
The CSS framework to be used in this project is Tachyons CSS. Install it by running npm install tachyons --save-dev
in the terminal.
Afterwards, make it globally available for usage in the project by adding the line below in our main.js
:
import tachyons/css/tachyons.css
Router Setup
We will be using Vue Router for the application routing by running this line of code in our terminal:
vue add router
After this, we will register it in our main.js
file as shown below:
import router from './router'
new Vue({
router,
render: h => h(App),
}).$mount('#app')
Cloudinary Setup
For our application’s asset management, if you haven’t already, you will need to create an account with Cloudinary by clicking here. Creating account is completely free.
To use Cloudinary in your project, install the Cloudinary Vue.js SDK with the command below before proceeding to configuring it in your Vue app.
npm install cloudinary-vue
Configuring Vue App for Cloudinary
- Create a
.env
file in the root of your project and add your Cloudinary Cloud name. You can find your cloud name by navigating to your Cloudinary dashboard. - Register the cloudname in your
.env
file like so:VUE_APP_CLOUDINARY_CLOUD_NAME ='****'
- Register Cloudinary component for global use in your
main.js
file by adding this: ```js
import Cloudinary from "cloudinary-vue";
Vue.use(Cloudinary, {
configuration: {
cloudName: "***",
secure: true
}
});
**NB**: *Ensure you replace all occurrences of “***" with your cloudname.*
- Create a folder named **“store**” in your media library on Cloudinary and keep all your images for the store there. You can get the images used in the project [**here**](https://res.cloudinary.com/moerayo/raw/upload/v1632444914/store-images.zip).
- For the creation of our mock API, we will need each image *public_id*.
If you are wondering where to find the **public_id** on your asset the marked yellow rectangle is the **public_id** and this is what we will be using to fetch the images for display in our application.
![how to find the public id in Cloudinary](https://paper-attachments.dropbox.com/s_A6667D2F33637C2482B84A0A30C16BAA593750143A3B98F984A8FD88E19606D4_1632682289004_uCRrLKDO.png)
## API Setup
A full-scale e-commerce website will have an API integration where all the application’s products will be fetched but setting that up is beyond the scope of this tutorial. Instead, we will create a mock API that would serve us just well.
Since each products will have some basic values, we will be creating our API to have a structure like this:
- id
- name
- size
- color
- price
- public_id
- quantityInStock
Navigate back to your project and in your `src` folder, create a folder named `api` then proceed to creating a file named `productfile.js` in the just created `api` folder.
**NB**: *You can get the data used by downloading via this* [*link*](https://res.cloudinary.com/moerayo/raw/upload/v1632554210/store/productfile_wlbbc5.js)
At the end, you should have something that looks like this:
```js
{
id: 1,
name:"Ore Sweater",
size: 'M, XL, XXL',
color: 'Custom made to what you want',
price: '15000',
public_id:"store/store4_rlkr3u",
quantityInStock: 20,
}
NB: These are just based on my own preferences, feel free to add more data or create yours.
Vuex Setup
As stated earlier, the applications state management will be handled with Vuex and this will be configured by running the code below in your terminal.
npm install vuex --save
After successful installation, proceed to creating a folder named store.js
in your src
folder and add a file named index.js
in it.
We also will be registering the plugin globally in your main.js
like so:
import store from '@/store/index'
new Vue({
router,
store,
render: h => h(App),
}).$mount('#app')
Auth0 Setup
Auth0 will handle the site’s authorization and authentication as such only authenticated users will be able to view the products catalog and purchase product.
To do this, we will do the following:
- Create an account on Auth0
- Install Auth0 SDK by running this command in the terminal:
npm install @auth0/auth0-spa-js
- After creating an account on Auth0, an application will be created for you or you can decide to create a new one yourself.
- Then proceed to configuring your Logout URLs, Allow Web Origins and Callback URLs with your project’s port which in this case will be
http://localhost:8080
- Then proceed to configuring your Logout URLs, Allow Web Origins and Callback URLs with your project’s port which in this case will be
- Create an authentication wrapper to make asynchronous methods of the Auth0 SDK easier to work with. Click here to read the great guide written by the amazing team at Auth0 for setting it up.
- Proceed to creating a new file called
auth_config.json
in your root folder and add yourclientId
anddomain
like so: ```js
{
"domain": "xxxx",
"clientId": "xxxx"
}
- Afterwards we will have to register the SDK for global usage in our `main.js` file by adding these:
```js
// Import the Auth0 configuration
import { domain, clientId } from "../auth_config.json";
// Import the plugin here
import { Auth0Plugin } from "./auth";
// Install the authentication plugin here
Vue.use(Auth0Plugin, {
domain,
clientId,
onRedirectCallback: appState => {
router.push(
appState && appState.targetUrl
? appState.targetUrl
: window.location.pathname
);
}
});
This completes the basic things we need to have ready for our authentication.
Mid Check
What have we gotten so far?
- At this stage, we have successfully created a Cloudinary account and configured it in our app.
- We have also successfully created the mock API for the project, integrated Vue Router and Vuex and configured all appropriately.
- We have also set up the basic scaffold for our user authentication using Auth0.
Now, let’s continue…
Creating the Navigation
It is important that users of our application are able to navigate to different sections of the application as seamlessly as possible.
In this section, we will be creating the site’s application by using the Vue router and integrating with Auth0.
At this point, you have to decide on the pages you would like users to see on your application and create the files accordingly in your views
folder.
For our e-commerce site, here are the things we want users to be able to see:
- The home page
- The products list page
- The user’s profile page
- The cart
Now that we have established that, navigate back to your components
folder and create a new file named nav.vue
where we will create a navigation bar by adding the following code:
<template>
<div>
<header class="bg-white black-80 tc pt4 avenir">
<a href="/">
<cld-image public-id="store/logo_ja9ugi"></cld-image>
</a>
<h1 class="mt2 mb0 baskerville i fw1 f1 mh2">Rayo E-Store</h1>
<h2 class="mt2 mb0 f6 fw4 ttc tracked i">Your satisfaction is our utmost pleasure...</h2>
<nav class="bt bb tc mw7 center mt4 flex justify-between flex-wrap">
<a class="f6 f5-l link bg-animate black-80 hover-bg-lightest-blue dib pv3 ph2 ph4-l" href="/">Home</a>
<a class="f6 f5-l link bg-animate black-80 hover-bg-washed-red dib pv3 ph2 ph4-l" v-if="$auth.isAuthenticated" href="">
<router-link class="link black" to="/products">Products</router-link>
</a>
<a class="f6 f5-l link bg-animate black-80 hover-bg-light-yellow dib pv3 ph2 ph4-l" v-if="$auth.isAuthenticated">
<router-link class="link black relative" to="/cart">Cart</router-link>
</a>
<a class="f6 f5-l link bg-animate black-80 hover-bg-light-pink dib pv3 ph2 ph4-l pointer" v-if="!$auth.isAuthenticated && !$auth.loading">
<span id="qsLoginBtn" @click.prevent="login"><i class="fas fa-sign-in-alt mr2"></i>Login</span>
</a>
<a class="f6 f5-l link bg-animate black-80 hover-bg-light-green dib pv3 ph2 ph4-l pointer" v-if="$auth.isAuthenticated">
<router-link class="link black" to="/profile">Profile</router-link>
</a>
<a class="f6 f5-l link bg-animate black-80 hover-bg-light-pink dib pv3 ph2 ph4-l pointer" v-if="$auth.isAuthenticated">
<img :src="$auth.user.picture" class="br-100 w1 h1" alt=""> <span id="qsLogoutBtn" href="#" @click.prevent="logout"> Log out </span>
</a>
</nav>
</header>
</div>
</template>
<script>
export default {
name: 'navigation',
methods: {
login() {
this.$auth.loginWithRedirect();
},
logout() {
this.$auth.logout();
this.$router.push({ path: "/" });
}
}
}
</script>
Updating the App.vue
with this component will render a view that looks like this in the browser
The final result in your web browser after this process should produce these:
- When user is not logged in.
- When user is logged in
Fetching Products.
Setting up Vuex for fetching products
To update the UI with the product, we will have to fetch the response from the productfile.js
. Vuex will be used to handle communication between the mocked API and the UI display.
Hence, we need to modify the store/index.js
file with this:
import Vue from "vue";
import Vuex from "vuex";
import shop from '@/api/productfile'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
products: [],
},
getters: {
availableProducts (state) {
return state.products
},
},
actions: {
fetchProducts ({commit}) {
return new Promise((resolve) => {
shop.getProducts(products => {
commit('setProducts', products)
resolve()
})
})
},
},
mutations: {
setProducts (state,products){
state.products = products
},
}
})
Above, we created a state named products
in store/index.js
, which has an initial empty array. The purpose of this is to store the responses we will be getting from the api/productfile.js
.
The fecthProducts()
method in our actions retrieve the responses which on successful fetch, the setProducts
method stores the response in the products
state.
Updating the UI
To update the UI with the fetched response, we will implement Vue’s mounted()
function which will display the results coming in through our computed property - products
.
Using Vue directives to iterate through the products array to render the products in our already created productcards.vue
component, we should have a ready products list page.
Now, let’s go ahead and add this code to our productcards.vue
file.
<template>
<div>
<div v-for="product in products" :key="product.id" class="dib">
<article class="br2 ba dark-gray b--black-10 mv3 w-100 w-90-m w-90-l mw5 center">
<cld-image :publicId="product.public_id" loadinng="lazy">
<cld-placeholder
type="blur">
</cld-placeholder>
<cld-transformation height="250" width="250" crop="fill" />
<cld-transformation :overlay="{url: 'https://res.cloudinary.com/moerayo/image/upload/v1632557532/store/logo-bgnone_kdje7n.png'}" width="0.4" gravity="south_east"/>
</cld-image>
<div class="pa2 ph3-ns pb3-ns bg-washed-red">
<div class="dt w-100 mt1">
<div class="">
<h3 class="f6 mv0">{{product.name}}</h3>
</div>
<div class="">
<p class="f5 mv1">₦{{product.price}}</p>
</div>
</div>
</div>
</article>
</div>
</div>
</template>
<script>
export default {
name: 'product',
computed: {
products() {
return this.$store.getters.availableProducts
}
},
mounted() {
this.$store.dispatch('fetchProducts')
}
}
</script>
The code above shows us mapping through the products array in an already created cards component of Tachyons CSS.
NB: You may have noticed a component named cld-image
which renders our image. This component is a Cloudinary image component and it has our cloudName
attribute, which points to the Cloudinary cloudName already registered in our .env
file.
In addition to the cloudName
, the component also has the public_id
attribute which is returned with our response from the mocked API. This public_id
is what cloudinary will use in conjunction with our cloudName
to render the images been pointed to at each iteration.
At this stage, we should have a products section that looks like this:
Creation of the cart
Our e-commerce is not complete without the functionality that allows the user to add products to cart and check out what they have added to their cart. To do this, we will:
- Update our store for the carting options.
At the end, we will have the following code in our store/index.js
.
//import Vue and dependencies here
export default new Vuex.Store({
state: {
// products: [],
cart: []
},
getters: {
/* availableProducts (state) {
return state.products
}, */
cartProducts (state) {
return state.cart
},
cartTotal (state, getters) {
return getters.cartProducts.reduce((total, product) => total + product.price * product.quantity, 0)
},
cartIteming(state){
return state.cart.length
}
},
actions: {
fetchProducts ({commit}) {
// return products
},
addProductToCart (context, product) {
if (product.quantityInStock > 0) {
const cartItem = context.state.cart.find(item => item.id === product.id)
if (!cartItem) {
context.commit('pushProductToCart', product)
} else {
context.commit('incrementItemQuantity', cartItem)
}
}
},
removeProduct (context, product) {
context.commit('popProductFromCart', product.id)
context.commit('incrementProductInventory', product)
},
removeCartProducts(context){
context.commit('removeAllProducts')
}
},
mutations: {
setProducts (state,products){
//product mutation here
},
pushProductToCart (state, product) {
state.cart.push({
id: product.id,
quantity: 1,
title: product.name,
price: product.price,
productprice: product.price,
newQuantityInStock: product.quantityInStock
})
},
popProductFromCart(state){
state.cart.pop()
},
removeAllProducts(state){
state.cart = []
},
incrementProductInventory (state, product) {
product.quantityInStock--
},
incrementItemQuantity (state, cartItem) {
const product = state.products.find(product => product.id === cartItem.id)
cartItem.quantity++
product.quantityInStock--
cartItem.productprice = cartItem.quantity * product.price
}
}
})
NB: Some codes were commented out as they have been added prior to this stage.
- Create a new a file and name it
cart-checkout.vue
in thecomponents
folder and add the following code to register the latest change we have made to thestore/index.js
<template>
<div>
<div class="ph4">
<h1 class="silver">Your Cart</h1>
<div class="overflow-auto">
<table class="f6 w-100 mw8 center" cellspacing="0">
<thead>
<tr class="stripe-dark">
<th class="fw6 tl pa3 bg-white">Product</th>
<th class="fw6 tl pa3 bg-white">Price</th>
<th class="fw6 tl pa3 bg-white">Quantity</th>
<th class="fw6 tl pa3 bg-white">Total</th>
</tr>
</thead>
<tbody class="lh-copy">
<tr class="stripe-dark" v-for="product in products" :key="product.id">
<td class="pa3">{{product.title}}</td>
<td class="pa3">₦{{product.price}}</td>
<td class="pa3">
<input v-model.number="product.quantity" min="1" :max="product.newQuantityInStock" type="number" id="quantity" class="form-control w-75 d-block" >
</td>
<td class="pa3">₦{{product.price * product.quantity}}</td>
<td class="pa3">
<i @click="removeProduct(product)" id="delete-item" class="fas fa-window-close text-danger fa-2x d-block ms-4"></i>
</td>
</tr>
</tbody>
</table>
</div>
<div v-if="!products.length">
<p class="bg-washed-red pv3 ph2 br2">No item in your cart!</p>
</div>
<div class="tl mw8 center w-100">
<div v-if="products.length>0" class="">
<p class=" f4"><span class="green fw6 mr2">Total:</span>₦{{total}}</p>
<button class="bg-washed-red bn br2 pv2 ph3 w-100 w5-ns red di-ns db mr3 link" @click="removeCartProducts()">
<i class="fas fa-trash"></i> Empty Cart
</button>
</div>
<router-link to="/products" class="link bg-green mt3 pv2 ph3 bn br2 white tc db dib-ns"><i class="fas fa-space-shuttle mr2"></i>Continue Shopping</router-link>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'checkout',
computed: {
products () {
return this.$store.getters.cartProducts
},
total () {
return this.$store.getters.cartTotal
},
cartNumber(){
return this.$store.getters.cartIteming
}
},
methods: {
removeProduct(product){
this.$store.dispatch('removeProduct', product)
},
removeCartProducts(){
this.$store.dispatch('removeCartProducts')
},
}
}
</script>
The code above creates a table where the items that have been added to the cart will be displayed while also allowing users modify the quantity of items in their cart as they deem fit.
- Register the component in the
cart.vue
file in yourviews
folder. ```js
import checkout from '@/components/cart-checkout.vue';
export default {
components: {
checkout
}
}
### Updating `productscards.vue` with the ‘add to cart’ function.
The current UI we have does not in any way give users the ability to add any item to cart but we will change this by updating our `productscards.vue` file to this:
```js
<template>
<div>
<div v-for="product in products" :key="product.id" class="dib">
<article class="br2 ba dark-gray b--black-10 mv4 w-100 w-90-m w-90-l mw5 center">
<cld-image :cloudName="(cloudName)" :publicId="product.public_id" width="400" height="250" crop="scale" />
<div class="pa2 ph3-ns pb3-ns bg-washed-red">
<div class="dt w-100 mt1">
<div class="">
<h3 class="f6 mv0">{{product.name}}</h3>
</div>
<div class="">
<p class="f5 mv1">₦{{product.price}}</p>
</div>
</div>
<div>
<button class="bg-black white bn pa2 w-70 br2 f7 fw2 mv2 pointer" @click="addProductToCart(product)"><i class="fab fa-opencart mr2"></i>Add to cart</button>
</div>
</div>
</article>
</div>
</div>
</template>
The above adds a “Add to Cart” button that allows the user add items to their cart. This button passes the necessary data like the product name, price and quantity we will want displayed in our carts page.
You will need to perform an action before adding items to cart and this can be done by adding this code in the script tag in your productscards.vue
file:
methods: {
addProductToCart(product) {
this.$store.dispatch('addProductToCart', product)
}
},
This method is responsible for adding items to the cart.
Your e-commerce website should look like this when you add items to the cart.
WHOOOT! Yes we did that!
Integrating Paystack for payment
What is an e-commerce site without a way to allow users pay for the items they have added to their cart?
To this end, we will use a payment platform called, Paystack. Paystck allows African businesses get paid by anyone from anywhere in the world.
Setting up Paystack.
If you haven’t before, go to Paystack and create a free account for your business then go ahead and copy the public key and add it the previously created .env
file in our root folder like so:
VUE_APP_PAYSTACK_KEY = 'xxx'
The public key can be gotten by navigating to the setting section on the Paystack Dashboard.
Integrating Paystack component
We will install Paystack in the project like so:
npm install --save vue-paystack`
```
To use the Paystack component for payment, we will navigate to the file we would like to show the Paystack component and import it there.
In this case, the file we would want to register it in is the `cart-checkout.vue` file in the components folder.
On addition, the file should look like this:
```js
<template>
<div>
<div class="ph4">
<h1 class="silver">Your Cart</h1>
<div class="overflow-auto">
<!-- The code to create the table goes here -->
</div>
<!-- code that checks for the producs length in cart goes here -->
<div class="tl mw8 center w-100">
<div v-if="products.length>0" class="">
<!-- code that calculates the total of carts goes here -->
<paystack class="bg-light-yellow gray pv2 ph3 bn br2 mr2 di-ns db w-100 w4-ns mb3" :amount="total*100" :full_name="$auth.user.name" :email="$auth.user.email" :paystackkey="PUBLIC_KEY" :reference="reference" :callback="processPayment" :close="close">
<i class="fas fa-money-bill-wave mr2"></i>Pay
</paystack>
<!--code to remove product from cart goes here-->
</div>
<!--router link that allows you access other parts of the applicarion based on your selected preference goes here -->
</div>
</div>
</div>
</template>
<script>
import paystack from 'vue-paystack';
export default {
// name: 'checkout',
data: () => {
return {
amount: null,
email: null,
full_name: null,
PUBLIC_KEY: 'pk_test_b75e40ec3847c3f94d28edbd98492c1687960563'
};
},
components: {
paystack,
},
computed: {
reference() {
let text = "";
let possible =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 10; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
},
//code for calculating the total,
// getting cart products
// and the cart number goes here
},
methods: {
processPayment: (response) => {
console.log(response)
},
close: () => {
console.log("You closed checkout page")
},
//code for removing products from the product table and removing products from cart goes here
}
}
</script>
```
The result of this should give you a Pay now button which when clicked upon will open a popup that allows the user pay for the items purchased.
**NB**: Some codes were commented out as they have been added prior to this stage.
![The checkout section showing the pay now button](https://paper-attachments.dropbox.com/s_A6667D2F33637C2482B84A0A30C16BAA593750143A3B98F984A8FD88E19606D4_1632682414781_kOK8ZxDE.png)
Take a look at what you’ve built and feel free to give yourself a pat on the back!
![after building celebratory gift](https://paper-attachments.dropbox.com/s_A6667D2F33637C2482B84A0A30C16BAA593750143A3B98F984A8FD88E19606D4_1632682428117_celebrate-icegif.gif)
## Conclusion
In this tutorial, we have successfully built an e-commerce application using the following core tools:
- Vue.js
- Cloudinary
- Auth
- Paystack
We were able to seamlessly use Auth0 for our application users authentication and integrated Cloudinary’s SDK to manage all media assets of the page while handling all transactions with the Paystack payment gateway.
The final product of this tutorial addressed the payment problem of goods faced by African businesses while employing other tools - Cloudinary and Auth0 - to make this experience as seamless as possible.
Here is a link to the live [demo](https://rayo-estore.netlify.app/) hosted on netlify for you to play around with.
## Additional Resources
- [Here’s](https://auth0.com/docs/quickstart/spa/vuejs/01-login) a great guide written by the awesome Auth0 team. It will be of great use if you’d like to know more about using Auth0 with Vue.
- Cloudinary is a really powerful tool and knowledge of it is something you will want to have under your belt. This [guide](https://cloudinary.com/documentation/vue_image_manipulation#adding_text_and_image_overlays) written by the great Cloudinary team will teach you how to harness it’s power.
Content created for the [Hackmamba](https://content.hackmamba.io/) Jamstack Content Hackathon with Auth0 and Cloudinary.
Top comments (0)