What I learned from the Ecommerce Website I built using Vue, PHP and MySQL(school project)
Marlo Badinas
Posted on June 25, 2024
Intro
What did I build?
The project Bossing's, an e-commerce website where users can browse shop products, add items to their cart, and checkout. The site includes a simple admin dashboard for easy order management.
Functions:
- Sign-in and sign-up functionality.
- Add to cart and checkout functionality.
- Admin dashboard that displays a summary of details and a list of orders in the shop.
- Inventory page that helps manage stock effectively and edit shops product details.
Background
This project was developed as a school requirement, we were told to build an e-commerce website demonstrating CRUD (Create, Read, Update, Delete) functionality using native PHP, an Apache server, and MySQL. I chose to create an online shop for Bossing's Chili Garlic Oil Sauce simply because the resources (images, data) are easily accessible due to the shop's proximity to my location, and familiarity of the business process flow.
What I learned
Frontend:
Stack: Vue, and TailwindCSS
Vue
I chose Vue.js for the front-end framework due to its simplicity and intuitive coding experience. Among the available framework options, Vue is the only option I considered, primarily because I had prior experience with it and wanted to apply the little knowledge I had with it in a practical project.
While building the project, I encountered lots of bugs and errors. Since I was still learning Vue as I code, these issues opened knowledge gaps that I addressed through research and community support. I had to refer to different sites even to make different programs work because I lacked the prerequisite knowledge and have wished someone had already answer.
For example when I was referencing static assets, such as images. I needed to display a logo on the home page, but my attempts to use various URLs resulted in 404 (Not Found) errors. Using local images in Vite, the bundler I was using, is more confusing than I thought. The simplest solution that worked for was importing each image as a variable, which I could then reference in the template. Here’s the code snippet I used for reference:
<script setup>
import imgUrl from '@/assets/img.png';
</script>
<template>
<img :src="imgUrl" />
</template>
The @
symbol refers to the src
folder. While this method works, an obvious drawback is the need to import each image individually if there are multiple images.
On the product page, I was supposed to dynamically load the products image, and the fetched product data was supposed to contain image_src
as a string to reference the image URL. However, this approach was not functioning correctly. To resolve this, I changed the value of each product’s image_src
to its corresponding name. I then used a helper function, getImageUrl
, to dynamically load the images based on the product’s image source name. Here’s the implementation:
import classic from '@/assets/img/classic.png';
import sweet from '@/assets/img/sweet.png';
import extra from '@/assets/img/extra.png';
function getImageUrl(name) {
if (name === 'classic') {
return classic;
} else if (name === 'sweet') {
return sweet;
} else {
return extra;
}
}
In the template, I called this helper function in the src
binding:
<div v-for="product in products" :key="product.id" >
<div class="h-80">
<img :src="getImageUrl(product.image_src)" :alt="product.image_description" />
</div>
</div>
Vue’s reusable components made the project easier to build and customize. However, I regret not fully leveraging this feature. Because I did not consider the project's scalability, I missed opportunities to componentize several reusable HTML codes. If I were to redo the project with scalability in mind, I would create more reusable components for future maintenance.
Backend:
Stack: PHP, Mysql, Axios
PHP
The web API endpoints that I built using native PHP were messy, and unorganized but it did it's job. I have multiple endpoints inside and in every endpoint,there should be a connection from the database. A simple solution I implemented, which I copied from one of the tutorial video I watched, was creating a separate config.php, and a Database.php . The configuration file stores the database connection details, such as the host, port, and database name. The Database.php
class accepts the configuration, username, and password as parameters during instantiation, and uses these to create a PDO database connection. Additionally, this class provides helper functions for querying and fetching data.
Example usage:
As you can see, in one of my endpoints, I only needed to require the two php file and create a Database instance to have a connection to the database.
User Access
I implemented a simple user level check in the client side to direct users to their respective view pages after logging in. Here's how it works:
if (data.userLevel === 1) {
router.push('/dashboard');
} else {
router.push('/home');
}
After the user logs in, their user level is checked. If the user's level is 1
, they are redirected to the dashboard. If the user's level is not 1
, they are redirected to the home page.
Data Validation
To ensure the input from the user is reliable, I implemented both client-side and server-side validation:
For the client-side, just simple check to ensure input fields are filled before submitting, while on the server-side, data sanitation to ensure the input data is clean. There were endpoints that lacks server-side validation, but the client-side validation was good enough for the project purposes.
Same-origin policy
Since my web API is hosted by Apache (on a different domain or port) and the client side is run using npm run dev
(typically on a different port),when I was testing my API endpoints, I encountered a CORS error. The browser sees these requests as coming from different origins and blocks them to prevent security risks. This error stems from a security mechanism that browsers implement called the same-origin policy.The same-origin policy fights one of the most common cyber attacks, which is the cross-site request forgery. With the help of chatgpt the solution implemented are:
As you can see above, the code checks if the request origin contains 'localhost'. If true, it sets the Access-Control-Allow-Origin
header to allow requests from that specific origin. This allows the API to accept requests from any localhost port. I added these multiple lines of code in every endpoint and then that solved the issue.
Database Relationship
I struggled with how to implement the relationship between order items (cart items) and orders (displayed in the dashboard). What I ended up doing is use the order_id
as a foreign key in the order items table. This allows multiple order items to belong to a single order. The logic follows this sequence, when a user adds a product to the cart, the order_id
for that item is initially set to null. When the user clicks the checkout button, the order_id
for each order item is updated based on the newly created order's order_id
. To display orders and their associated order items in the dashboard, I fetch all orders first and then loop through them, fetching their associated order items using an inner join in the SQL query. However, one downside of this approach is that when a user removes order items from their cart page, the associated data also disappears from the admin dashboard. Regrettably, I haven't resolved this issue yet. This simple approach was the only solution I could think of at that moment.
Tech/frameworks I discovered and tried using
Axios
Thankfully I'm familiar with Axios, a widely-used library. It simplified working with REST APIs, making the processes much easier.
Pinia
I had the opportunity to work with Pinia, a state management library, because I needed a way to store the user_id
of the current user upon logging in. Pinia's stores made this possible. Whenever I need to retrieve the current user_id
,I simply access it from the store I defined.
Vue-Router
Vue Router is the official client-side routing solution for Vue. You configure routes to tell Vue Router which components to show for each URL path. It checks the URL and, depending on what's entered there, displays a different component on the screen. This allowed the project to smoothly switch between components.
Postman
I used Postman to manually test the API endpoints instead of always checking the inspect section of my browser. This helped me identify issues more easily and faster. Often, errors were caused by mismatched spellings between the URLs I passed to Axios and the actual PHP endpoints, or by the CORS issues I mentioned earlier.
Conclusion
It has been such a rewarding experience, after solving bugs, rendering views, and getting routes to work, it feels satisfying. I'm also learning on the go with the tech stack I'm using for the current project, and I realized that I learn and master a technology faster when I'm actively using it. Encountering bugs while coding reveals knowledge gaps, which I can then fill through research. This simple project was far from being perfect, and there's a lot of improvements that I could have done, and I'm eager to implement those improvements in the future projects.
Here are some resources I referenced when writing this:
Posted on June 25, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.