In the previous part of this series, I introduced Appwrite and explained multiple definitions and logic. In case you didn't read the previous article and you don't know what is BaaS or Appwrite, make sure to check it out to have a better understanding.
In this series, we're going to code and utilize amazing Appwrite features to build a complete E-Commerce website. Every part of the series will cover one or more parts of our website.
🚨 Prerequisites:
Do you like looking directly at source code? Here is the source code for this tutorial:
I have created Github branches for each section in case you would like to look at the source code for this specific section only.
We're building this Appwrite E-Commerce website with VueJS and Tailwind CSS. A prior knowledge of these technologies will definitely help you understand this tutorial better. If you don't have any background in regards to Tailwind or Vue, don't worry about it and just follow along and try to keep you mind open.
I have created Github branches for each section in case you would like to look at the source code for this specific section only.
We're building this Appwrite E-Commerce website with VueJS and TailwindCSS. A prior knowledge of these technologies will definitely help you understand this tutorial better. If you don't have any background in regards to Tailwind or Vue, don't worry about it, just follow along and try to keep you mind open.
My main focus during this tutorial is Appwrite integration and features utilization.
1- E-Commerce Frontend Skeleton:
I have grabbed multiple components online into VueJS 3 project to have my tailwind frontend ready with what I need. Mainly the design is fetched from a free E-Commerce Tailwind product on Gumbroad by Halit Güvenilir.
This will give me the kick start I need to use Appwrite and work with its APIs. I have done modifications to the original design to fit my tutorial, if you would like to have a copy of the final skeleton, Just head to the tutorial repo on Github and fork the master branch.
Keep in mind that I might do several changes to the design as we go through our tutorial, this depends on what exactly we're trying to accomplish.
Feel free to explore the repo as I already set up few routes for the authentication page, product view page, admin overview page, add new product page and cart page:
After PowerShell command is done, It will ask you about the server HTTP port and other things. I will just stick with the defaults without changing anything. This eventually will allow you to open Appwrite's console on port 80 by default (localhost:80).
Enter in the browser localhost:80 and signup to Appwrite's console. Press on create project and choose a name and ID for your project. In my case I named it appwrite-ecom.
Following the documentation to install Web SDK, I hit the below command in the project folder.
npm install appwrite
I created .js file to import Appwrite Web SDK in order to be able to use it in my VueJS components.
src/utils.js
import{Appwrite}from"appwrite";constappwrite=newAppwrite();appwrite.setEndpoint("http://localhost/v1")// Your API Endpoint.setProject("appwrite-ecom");// Your project IDexport{appwrite};
If you would like to start coding with me from here, simply head to the Github repo and fork the appwrite-installed branch to have your own copy of this project with appwrite installed.
3- Authentication:
Now, It's time to start working on our E-Commerce website. The first thing to do is to set up authentication for website's users. Appwrite made this task extremely easy for developers with their Web SDK.
Let's head to src/components/main/Authpage.vue, You will find that it's only a VueJS component where the template includes some code to show signin/signup forms.
In the above code, we're using a Vue click event to show and hide the forms upon the user click. If the user clicked on 'Sign Up' show the sign up form, and if the user clicked on 'Sign In' show the sign in form.
Script tags only contains the components we're using and setting pageSwitch to false to show the Sign in form by default.
We need now to add few methods using Appwrite's Web SDK to allow users to login and signup. We have this parent component Authpage.vue that will include all the methods while the other child components Login.vue and Signup.vue will only pass the required data from the forms to the parent component to trigger it's methods.
First thing, let's create a method to check if the user already logged in or not as soon as they open the page. This will help us provide a response once the user login or signup. In our case, we want to save the logged in user details in order to fetch them whenever we want and take the user back to the page they were on before logging in.
We need to create src/store.js file to store the user details in as a shared information across all components in case we needed it in any of them.
Let's import Appwrite from src/utils.js in our Authpage.vue file:
Authpage.vue
importappwritefrom"../../utils";
Now we're going to use appwrite's method Get Account to check if the user is logged in, if yes then save it to store.js and take the user back to the page they were browsing before, if no let me know the error in the console.
To trigger this checkLogin method that we're creating, I have used Vue lifecycle mounted hook.
The script now will look like this:
Authpage.vue
<script>importLoginfrom"../Login.vue";importSignupfrom"../Signup.vue";import{appwrite}from"../../utils";import{store}from"../../store.js";exportdefault{name:"Authpage",components:{Login,Signup,},data:()=>{return{pageSwitch:false,};},mounted:function (){this.checkLogin();},methods:{asynccheckLogin(){try{// Getting the currently logged in accountconstresponse=awaitappwrite.account.get();// If there is a response, save it to store.jsstore.userprofile=response;// Once action done, get the user back to the previous page.this.$router.go(-1);}catch (err){if (err=="Error: Unauthorized")return;console.error(err);}},},};</script>
Now if you head in your browser to /auth which is a route already set in router/index.js you will see the below appwrite response error in the console because there isn't any logged in user to check:
User (role: guest) missing scope (account)
at Appwrite.eval
let's create the first method for signing up. According to Appwrite documentation, we need to create an account and we're able to pass userId, email, password and name. In my case, Instead of passing a userId I will allow users to insert a username in the signup form and I will use this username as a userId.
Also we need to create a session for this logged in user.
Let's create our method and get it ready to receive the information we need to pass to Appwrite:
Now let's continue to have all of our methods ready the same way we did with signup method, eventually we will have this result:
Authpage.vue
<template><div><divv-if="!store.userprofile"><Loginv-if="!pageSwitch"><div><p>{{pageSwitch?"Got an account?":"Haven't got an account?"}}<spanclass="cursor-pointer"@click="() => (this.pageSwitch = !this.pageSwitch)">{{pageSwitch?"Login":"Sign Up"}}</span
></p>
</div>
</Login>
<Signupv-if="pageSwitch"><div><p>{{pageSwitch?"Got an account?":"Haven't got an account?"}}<spanclass="cursor-pointer"@click="() => (this.pageSwitch = !this.pageSwitch)">{{pageSwitch?"Login":"Sign Up"}}</span
></p>
</div>
</Signup>
</div>
</div>
</template>
<script>import{appwrite}from"../../utils";import{store}from"../../store.js";importLoginfrom"../Login.vue";importSignupfrom"../Signup.vue";exportdefault{name:"Authpage",components:{Login,Signup,},data:()=>{return{pageSwitch:false,store,};},mounted:function (){this.checkLogin();},methods:{// Logout the current logged in user in store.jsasynclogout(){store.userprofile=false;appwrite.account.deleteSession("current");},asynclogin(){try{awaitappwrite.account.createSession(event.target.email.value,event.target.password.value);this.checkLogin();returntrue;}catch (error){console.error(error);returnfalse;}},asyncsignup(username,nameOfUser,password,email){try{awaitappwrite.account.create(username,email,password,nameOfUser);awaitappwrite.account.createSession(email,password);this.checkLogin();}catch (error){console.error(error);returnfalse;}},// Using username to have a dynamic routes for user profilesasyncupdateUsername(username){try{awaitappwrite.account.updatePrefs({username:username,});}catch (error){console.error(error);returnfalse;}},asynccheckLogin(){try{constresponse=awaitappwrite.account.get();store.userprofile=response;// Once action done, get the user back to the previous page.this.$router.go(-1);}catch (err){if (err=="Error: Unauthorized")return;console.error(err);}},},};</script>
Now in the src/components/Login.vue file, We're going to trigger the parent's login method and inject it with the required data from the form.
<template><mainclass="relative"><divclass="max-w-screen-xl px-4 py-10 mx-auto sm:px-6 lg:px-8"><divclass="max-w-lg mx-auto"><h1class="text-2xl font-bold text-center text-black sm:text-3xl">Signup</h1>
<pv-if="error"class="text-red-500 text-sm">{{error}}</p>
<form@submit="processRegister"action=""class="p-8 mt-6 mb-0 space-y-4 rounded-lg shadow-2xl dark:bg-gray-700/50 backdrop-blur-md bg-none dark:text-white"><pclass="text-lg font-medium">Signupnewaccount</p>
<div><labelfor="nameOfUser"class="text-sm font-medium">Username</label>
<divclass="relative mt-1"><inputv-model="username"type="text"id="username"requiredplaceholder="Enter username"class="appearance-none border border-gray-200 bg-gray-100 rounded-md py-2 px-3 hover:border-gray-400 focus:outline-none focus:border-gray-400 w-full"/></div>
</div>
<div><labelfor="nameOfUser"class="text-sm font-medium">YourName</label
><divclass="relative mt-1"><inputv-model="nameOfUser"type="text"id="nameOfUser"requiredplaceholder="Enter your name"class="appearance-none border border-gray-200 bg-gray-100 rounded-md py-2 px-3 hover:border-gray-400 focus:outline-none focus:border-gray-400 w-full"/></div>
</div>
<div><labelfor="email"class="text-sm font-medium">Email</label>
<divclass="relative mt-1"><inputv-model="email"type="email"id="email"requiredclass="appearance-none border border-gray-200 bg-gray-100 rounded-md py-2 px-3 hover:border-gray-400 focus:outline-none focus:border-gray-400 w-full"placeholder="Enter email"/></div>
</div>
<div><labelfor="password"class="text-sm font-medium">Password</label>
<divclass="relative mt-1"><inputv-model="password"type="password"id="password"requiredclass="appearance-none border border-gray-200 bg-gray-100 rounded-md py-2 px-3 hover:border-gray-400 focus:outline-none focus:border-gray-400 w-full"placeholder="Enter password"/></div>
</div>
<div><labelfor="confirmPassword"class="text-sm font-medium">ConfirmPassword</label
><divclass="relative mt-1"><inputv-model="confirmPassword"type="password"id="confirmPassword"requiredplaceholder="Confirm Password"class="appearance-none border border-gray-200 bg-gray-100 rounded-md py-2 px-3 hover:border-gray-400 focus:outline-none focus:border-gray-400 w-full"/></div>
</div>
<buttonclass="px-4 py-2 text-center w-full inline-block text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700"type="submit">Signup</button>
<slot/></form>
</div>
</div>
</main>
</template>
<script>exportdefault{name:"Signup",data:()=>{return{username:"",nameOfUser:"",email:"",password:"",confirmPassword:"",loading:"",error:false,};},methods:{asyncprocessRegister(e){e.preventDefault();// Validationif (this.loading){return;}// Password confirmationif (this.password!==this.confirmPassword){this.error="Error: Passwords must be matching.";return;}// Length Validationif (!(this.password.length>=6&&this.password.length<=32)){this.error="Error: Password must be between 6 and 32 characters.";return;}if (this.nameOfUser.length>=100){this.error="Error: Name can not exceed 100 characters";return;}if (this.username.length>=50){this.error="Error: username can not exceed 50 characters";return;}this.loading=true;//I need a username here to make dynamic users routesletusername=this.username;username=username.replace(/\s+/g,"").toLowerCase();if ((awaitthis.$parent.signup(username,this.nameOfUser,this.password,this.email))===false){this.error="Something went wrong while registering, Check console for more details.";}else{setTimeout(()=>{this.$parent.updateUsername(username);},300);}},},};</script>
Now our authentication system is working perfectly fine, you can text this by heading to /auth and click on signup then insert whatever details you want and Voilà! You're logged in 🥳
Now we need to head to src/components/Header.vue and show My Cart button in the header only if the user is logged in and hide Sign In as well.
Now You can logout by pressing on the logout button and once you're logged out, you will see Sign in button again.
With that, we've finished our authentication system in our E-Commerce website and we're ready to start working on the shop, cart, products and admin panel and that's exactly what we're going to do in the next part of this series.
Let me know in the comments if you have any questions and I hope this was helpful for you :)