How To Create A Laravel 10 and Vue Js 3 E-commerce App

how to Create a Laravel 10 and Vue Js 3 E-commerce App

Hello there guys!!! in this tutorial, I will show how to Create a Laravel 10 and Vue Js 3 E-commerce App. This is the course outline…

Part 1: Setting up the Development Environment

  1. Setting up Laravel
      • Installing PHP and Composer.
    • Installing Laravel Installer.
    • Creating a new Laravel project.
  2. Setting up Vue.js
    • Installing Node.js and npm.
    • Adding Vue.js to the Laravel project using Laravel Mix or creating a separate Vue.js project.
  3. Configuring Laravel for API development
    • Setting up the database.
    • Setting up Laravel Sanctum for API Authentication.

Part 2: Building the REST API with Laravel

  1. Designing the Database Schema
    • Design the tables and relationships.
  2. Building the API
    • Creating migrations and models.
    • Setting up API Routes.
    • Building API controllers and defining CRUD operations.
    • Validating request data.
    • Implementing authentication using tokens.
    • Testing API endpoints using Postman or another REST client.

Part 3: Building the Frontend with Vue.js

  1. Introduction to Vue.js
    • Basic concepts: Components, Directives, Events.
  2. Setting up Vue.js in the Project
    • Installing necessary dependencies.
    • Configuring webpack with Laravel Mix if using in the same project.
  3. Building Components
    • Creating Vue.js components for different UI elements.
  4. Communicating with the API
    • Making HTTP requests to the Laravel API using axios or another HTTP client.
    • Handling responses and rendering data on the frontend.
  5. User Authentication
    • Building a login component.
    • Implementing Authentication with the API.
    • Protecting routes and handling authentication state.

Part 4: Bringing it All Together

  1. Integrating Frontend and Backend
    • Make sure the Vue.js frontend can communicate with the Laravel API.
  2. Error Handling
    • Implement proper error handling on both the frontend and backend.
  3. Deployment
    • Guide on deploying the full-stack application.
    • Suggestions for hosting services.
  4. Conclusion
    • Recap of what was learned.
    • Suggestions for further improvement and learning.

Part 1: Setting up the Development Environment For Laravel 10

In this part let’s start with Laravel for the backend. Before we begin, ensure that you have PHP installed on your system, as Laravel is a PHP framework. You will also need Composer, which is a dependency manager for PHP.

Step 1: Install Laravel

First, let’s install Laravel using Composer. Open your terminal and enter the following command:

composer global require laravel/installer

This command will install the Laravel installer globally on your system.

Step 2: Create a New Laravel Project

Now, let’s create a new Laravel project. Run the following command, replacing your_project_name with the desired project name:

laravel new EcommerceApp

This command creates a new directory with your project name and installs a fresh Laravel installation in that directory.

Step 3: Running the Laravel Application

Navigate to the newly created project directory:

cd EcommerceApp

Now, let’s run the Laravel development server:

php artisan serve

You should see an output indicating that the server is running. By default, it runs on http://127.0.0.1:8000. Open this URL in your web browser to see the Laravel welcome page.

Step 5: Setting Up a Database

Let’s set up a database for our application. You can use any database that Laravel supports. In this tutorial, we’ll use MySQL. You can create a new database using phpMyAdmin or the command line.

After creating the database, open the .env file in the root of your Laravel project. Update the following lines with your database connection information:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=ecommerceapp
DB_USERNAME=root
DB_PASSWORD=

Now, let’s run the migrations to create the necessary tables:

php artisan migrate

Part 2: Building the REST API with Laravel.

In this part, we’ll create the backend for our simple e-commerce application. We’ll create models, migrations, and controllers, and define routes for our API endpoints.

Step 1: Create Product Model and Migration

We’ll need a Product model to represent the items in our e-commerce store. Let’s generate a model along with its migration file:

php artisan make:model Product -m

This command creates a new model in the app/Models directory and a migration file in the database/migrations directory.

Product.php

class Product extends Model
{
 use HasFactory;
 protected $fillable = [
 'name', 'description', 'price',
 ];

}

In the Laravel framework, the $fillable property of a model class is an array that contains all the attributes that can be mass assignable.

Mass assignment is a feature that allows you to create or update multiple model attributes in a single statement, such as when you create a new model from an array of attributes:

Product::create(['name' => 'Product 1', 'description' => 'Description of product 1', 'price' => 19.99]);

In this case, if the name, description, and price fields are not specified in the $fillable property of the Product model, this operation will fail. This is a security feature of Laravel to prevent unauthorized updates to model attributes.

Therefore, the line:

protected $fillable = [ 'name', 'description', 'price', ];

…is specifying that the name, description, and price attributes of the model are allowed to be mass assigned. Any other attributes not included in this array will not be affected by mass assignment operations.

Now, open the migration file and define the columns for the products table. For our example, we’ll just keep it simple with a name, description, and price:

public function up()
{
   Schema::create('products', function (Blueprint $table) {
     $table->id();
     $table->string('name');
     $table->text('description');
     $table->decimal('price', 8, 2);
     $table->timestamps();
});
}

$table->decimal('price', 8, 2); is a way of defining a new column in a database table, where:

  • 'price' is the name of the column.
  • 8 is the total number of digits that the ‘price’ field can hold.
  • 2 is the number of digits after the decimal point.

So, in this case, the price field can hold a number up to 999999.99. This field can hold a maximum of 8 digits in total, of which 2 digits are reserved for after the decimal point. This means it can hold a maximum of 6 digits before the decimal point.

Now run the migrations:

php artisan migrate

This will create the products table in the database with the columns we defined.

Step 2: Create a Product Controller

We need a controller to handle the logic for our API. Create a controller using Artisan:

php artisan make:controller ProductController --api

This command generates a controller with the methods required for a RESTful resource (index, create, store, show, edit, update, destroy).

Step 3: Define Routes

Open the routes/api.php file and define the routes for our product resource:

use App\Http\Controllers\ProductController;

Route::apiResource('products', ProductController::class);

This single line generates all the necessary routes for a RESTful resource. You can run this command to inspect them…

php artisan route:list

Step 4: Implement Controller Methods

Now let’s implement the methods in ProductController. This is how you go about…

In ProductController, implement these CRUD methods:

<?php


namespace App\Http\Controllers;


use App\Models\Product;
use Illuminate\Http\Request;


class ProductController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
    return response()->json(Product::all(), 200);
}


/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
   $validatedData = $request->validate([
      'name' => 'required|max:255',
      'description' => 'required',
      'price' => 'required|numeric',
]);


  $product = Product::create($validatedData);


  return response()->json($product, 201);
}


/**
* Display the specified resource.
*/
public function show(string $id)
{
    // Retrieve the product from the database using the provided $id
    $product = Product::find($id);


     // Check if the product was found
    if ($product) {
   // Return the product as a JSON response with a 200 HTTP status code
        return response()->json($product, 200);
    } else {
// Return a 404 Not Found HTTP status code if the product was not found
   return response()->json(['message' => 'Product not found'], 404);
}
}


/**
* Update the specified resource in storage.
*/
public function update(Request $request, Product $product)
{
// Validate the request data
    $validatedData = $request->validate([
      'name' => 'required|max:255',
      'description' => 'required',
      'price' => 'required|numeric',
]);


// Check if the product was found
if ($product) {
// Update the product with the validated data
$product->update($validatedData);


  // Return the updated product as a JSON response with a 200 HTTP status code
   return response()->json($product, 200);
} else {
  // Return a 404 Not Found HTTP status code if the product was not found
  return response()->json(['message' => 'Product not found'], 404);
}
}


/**
* Remove the specified resource from storage.
*/
public function destroy(Product $product)
{
  // Check if the product was found
  if ($product) {
  // Delete the product
  $product->delete();


  // Return a 204 No Content HTTP status code
  return response()->json(null, 204);
} else {
  // Return a 404 Not Found HTTP status code if the product was not found
  return response()->json(['message' => 'Product not found'], 404);
}
}
}

Now you have all the CRUD methods implemented in your ProductController. These methods handle creating, reading, updating, and deleting products.

I did a video earlier on Laravel 10 REST API a comprehensive guide. So you can test the api. For example…
  1. List all products (GET request):http://127.0.0.1:8000/api/products
  2. Create a new product (POST request):http://127.0.0.1:8000/api/products
    • In Postman, choosePOSTas the request method.
    • The body of the request should be a JSON object that includes ‘name’, ‘description’ and ‘price’, like so:
    • key and value fields fill them with the data below…
    • { 
         "name": "Product Name", 
          "description": "Product Description", 
          "price": 99.99 
      }
      

      3. Show a single product (GET request):http://127.0.0.1:8000/api/products/{product}

        • Replace{product}with the ID of the product you want to retrieve.
        • e.g http://127.0.0.1:8000/api/products/1

      4. Update a product (PUT or PATCH request):http://127.0.0.1:8000/api/products/{product}

    • Replace{product}with the ID of the product you want to update.
    • e.g http://127.0.0.1:8000/api/products/1
    • The body of the request should be a JSON object that includes the fields you want to update, for example:
    • { 
          "name": "New Product Name", 
           "price": 149.99 
      }

      since we have validation in our laravel the update will ensure you add all the three fields but if you don’t want you can disable the validation probably on certain data or all of them

    • e.g
       'name' => 'required|max:255',
      
       // 'description' => 'required',
      
       // 'price' => 'required|numeric',
    • 5. Delete a product (DELETE request):http://127.0.0.1:8000/api/products/{product}
      • Replace{product}with the ID of the product, you want to delete.
      • e.g http://127.0.0.1:8000/api/products/1

Laravel determines if a request is an AJAX request by checking if theX-Requested-Withheader is set toXMLHttpRequest.

In Postman, ensure that you are setting the headerX-Requested-WithtoXMLHttpRequest. You can do this by going to the “Headers” tab in Postman and adding a new key-value pair:

X<span class="token operator">-</span>Requested<span class="token operator">-</span>With<span class="token punctuation">:</span> XMLHttpRequest<span class="token punctuation">.</span>

This should ensure that Laravel treats the request as an AJAX request and returns validation error messages in JSON format, which can be easily read in Postman.

Authentication using Laravel Sanctum

To implement authentication using Laravel Sanctum, you need to set up the backend by adding routes and controllers that handle registration, login, and logout. If you haven’t already set up Sanctum, you’ll need to follow the initial steps for installing and configuring it as well.

Here’s how you can setup your Laravel backend:

  1. Install Sanctum:If you haven’t already, you’ll need to install Sanctum:
    composer require laravel/sanctum

    Then, publish the Sanctum configuration and migration files:

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

Run the migrations:

php artisan migrate

Add the Laravel\Sanctum\HasApiTokens trait to your App\Models\User model:

namespace App\Models;

use Laravel\Sanctum\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
}

Adding the Laravel\Sanctum\HasApiTokens trait to the User model enables the model to authenticate requests through API tokens, providing a simple and secure way to manage token-based authentication for the user.

Configure CORS:

Modify the cors.php config to allow requests from your frontend domain. For example:

'paths' => ['api/*', 'sanctum/csrf-cookie'],

'allowed_origins' => ['http://localhost:8080'], // or your frontend’s URL

'supports_credentials' => true,
  • 'paths' => ['api/*', 'sanctum/csrf-cookie'],: This specifies the paths that should be accessible cross-origin. In this case, all routes under api/ and the sanctum/csrf-cookie route are accessible from other domains.
  • 'allowed_origins' => ['http://localhost:8080'],: This specifies which origins are allowed to access the resources. In this example, only requests from http://localhost:8080 are allowed.
  • 'supports_credentials' => true,: This means that the browser should include credentials like cookies or HTTP authentication headers in the request. It’s vital for Sanctum, Laravel’s authentication package, as it relies on cookies to authenticate users.

Create AuthController:

Generate a new controller:

php artisan make:controller AuthController

In AuthController.php, add methods for registration, login, and logout:

// Include necessary namespaces
namespace App\Http\Controllers;

use Illuminate\Http\Request; // For handling HTTP requests
use Illuminate\Support\Facades\Auth; // For handling authentication
use App\Models\User; // User model

// Define AuthController class which extends Controller
class AuthController extends Controller
{
// Function to handle user registration
public function register(Request $request)
{
// Validate incoming request fields
    $request->validate([
    'name' => 'required|string|max:255', // Name must be a string, not exceed 255 characters and it is required
    'email' => 'required|string|email|max:255|unique:users', // Email must be a string, a valid email, not exceed 255 characters, it is required and it must be unique in the users table
    'password' => 'required|string|min:6', // Password must be a string, at least 6 characters and it is required
]);

// Create new User
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password), // Hash the password
]);

// Return user data as JSON with a 201 (created) HTTP status code
return response()->json(['user' => $user], 201);
}

// Function to handle user login
public function login(Request $request)
{
// Validate incoming request fields
$request->validate([
'email' => 'required|string|email', // Email must be a string, a valid email and it is required
'password' => 'required|string', // Password must be a string and it is required
]);

// Check if the provided credentials are valid
if (!Auth::attempt($request->only('email', 'password'))) {
// If not, return error message with a 401 (Unauthorized) HTTP status code
return response()->json(['message' => 'Invalid login details'], 401);
}

// If credentials are valid, get the authenticated user
$user = $request->user();
// Create a new token for this user
$token = $user->createToken('authToken')->plainTextToken;

// Return user data and token as JSON
return response()->json(['user' => $user, 'token' => $token]);
}

// Function to handle user logout
public function logout(Request $request)
{
  // Delete all tokens for the authenticated user
  $request->user()->tokens()->delete();

   // Return success message as JSON
   return response()->json(['message' => 'Logged out']);
}
}

Setup Routes:

In the routes/api.php file, add routes for registration, login, and logout:

use App\Http\Controllers\AuthController;

Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
Route::middleware('auth:sanctum')->post('/logout', [AuthController::class, 'logout']);

You can navigate to the following urls…

http://127.0.0.1:8000/api/register (to register a user  for example…) Method: Post

name: Jared Jason

email: Jared@admin.com

password: password

Headers: Key: X-Requested-With Value: XMLHttpRequest

Authorization: Type Bearer Token,  Token itself

http://127.0.0.1:8000/api/login (to login)  Method: Post

email: Jared@admin.com

password: password
{
"user": {
"id": 8,
"name": "Jared Jason",
"email": "Jared@admin.com",
"email_verified_at": null,
"created_at": "2023-07-14T08:14:27.000000Z",
"updated_at": "2023-07-14T08:14:27.000000Z"
},
"token": "10|hqqVZchMOJcuO29gUtAo9mKonp9T2DBYo9CQMt1U"
}

Logging Out

copy the token and paste it into the Bear Token under Authorization. Change the URL to logout e.g

http://127.0.0.1:8000/api/logout?email=brian@brian.com&password=password

Output:

{

 "message": "Logged out"

}

If you try to logout without the token this is what you will receive…

{

 "message": "Unauthenticated."

}

Part 3: Building the Frontend with Vue.js.

Step 1: Create a Vue.js Project

First, let’s create a new Vue.js project. Make sure you have Node.js and npm installed. Then, use the Vue CLI to create a new project:

To install Vue CLI via npm, first, make sure you have Node.js and npm installed. You can check if Node.js and npm are installed by running:

node -v
npm -v

If they are not installed, you can download and install Node.js from the official website or use a package manager.

Once you have Node.js and npm installed, you can install the Vue CLI globally by running:

npm install -g @vue/cli

The -g flag installs the package globally, making the vue command available system-wide.

Now, the vue command should be available. You can check the version of the Vue CLI installed by running:

vue --version

After installing the Vue CLI, you can create a new Vue.js project by running:

vue create ecommerce-frontend

Navigate into the project directory:

cd ecommerce-frontend 

This will start the project creation process. Follow the prompts and select the features you need for your project.

“Alright, now we are presented with a few options for setting up our Vue.js project. Since we want to use the latest version of Vue, which is Vue.js 3, we should choose the first option ‘Default ([Vue 3] babel, eslint)’. This preset includes Vue 3, and also sets up Babel and ESLint for us. Babel is a JavaScript compiler that lets us use the latest JavaScript features, and ESLint is a tool for identifying and fixing problems in our JavaScript code. These tools are quite helpful and are common in modern JavaScript development. So, let’s go ahead and select the first option and hit enter.”

At this point, you can use the arrow keys to highlight the “Default ([Vue 3] babel, eslint)” option and then press the Enter key to continue with the setup.

Step 2: Install Axios

We’ll use Axios to make HTTP requests to our Laravel API. Install it via npm:

npm install axios

Let’s establish a base URL for our Axios API endpoints…

Create a file called `..src/axios.js`

import axios from 'axios';
const instance = axios.create({
baseURL: 'http://localhost:8000/api'
});


export default instance;

Step 3: Create Components

Inside your Vue.js project, let’s create different components for different functionalities. Create the following components inside the src/components directory:

  • ProductList.vue (to list all products)
  • ProductDetails.vue (to view details of a single product)
  • AddProduct.vue (to add a new product)
  • EditProduct.vue (to edit an existing product)

Step 4: Create Login and Registration Components

Inside your Vue.js project, let’s create different components for different functionalities. Create the following components inside the src/components directory:

..src/components/Login.vue

<template>
<!-- Form for login -->
<div class="form-container">
<form @submit.prevent="login" class="login-form">
<!-- Email and password inputs -->
<input type="email" v-model="email" placeholder="Email" required />
<input type="password" v-model="password" placeholder="Password" required />
<!-- Submit button -->
<button type="submit">Login</button>
</form>
</div>
</template>


<script>
import axios from '@/axios';


export default {
data() {
return {
// Data model for the form inputs
email: "",
password: ""
};
},
methods: {
async login() {
try {
// Making POST request to "/login" endpoint with email and password as data
const response = await axios.post("/login", {
email: this.email,
password: this.password
});


// If a token is received, it's stored in local storage and the user is considered logged in
if (response.data.token) {
localStorage.setItem('token', response.data.token);


// Committing a mutation to update 'isLoggedIn' state
this.$store.commit('LOGIN');


// Redirecting to the home page
this.$router.push('/');
}
} catch (error) {
console.error("An error occurred:", error);
}
}
}
};
</script>



<style scoped>
.form-container {
display: flex;
justify-content: center;
align-items: center;
}


.login-form {
display: flex;
flex-direction: column;
gap: 20px;
width: 300px;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}


.login-form input, .login-form button {
padding: 10px;
border-radius: 5px;
border: 1px solid #ccc;
font-size: 16px;
}


.login-form button {
background-color: #007BFF;
color: white;
cursor: pointer;
}
</style>

..src/components/Register.vue

<template>
<!-- Form for registration -->
<div class="form-container">
<form @submit.prevent="register" class="register-form">
<!-- Name, email, and password inputs -->
<input type="text" v-model="name" placeholder="Name" required />
<input type="email" v-model="email" placeholder="Email" required />
<input type="password" v-model="password" placeholder="Password" required />
<!-- Submit button -->
<button type="submit">Register</button>
</form>
</div>
</template>


<script>
import axios from '@/axios';


export default {
data() {
return {
// Data model for the form inputs
name: "",
email: "",
password: ""
};
},
methods: {
async register() {
try {
// Making POST request to "/register" endpoint with name, email, and password as data
const response = await axios.post("/register", {
name: this.name,
email: this.email,
password: this.password
});
// Here you could handle the response, for example, store the received token,
// update the 'isLoggedIn' state, and redirect to the dashboard or any other page
}
catch (error) {
console.error("An error occurred:", error);
if (error.response) {
console.error('Error details:', error.response.data);
}
}
}
}
};
</script>



<style scoped>
.form-container {
display: flex;
justify-content: center;
align-items: center;
}


.register-form {
display: flex;
flex-direction: column;
gap: 20px;
width: 300px;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}


.register-form input, .register-form button {
padding: 10px;
border-radius: 5px;
border: 1px solid #ccc;
font-size: 16px;
}


.register-form button {
background-color: #007BFF;
color: white;
cursor: pointer;
}
</style>

Step 5: Set Up Vue Router

Install and set up Vue Router to navigate between different components:

npm install vue-router@next

Create a router.js file in the src directory and set up routes for different components.

.src/router.js

import { createRouter, createWebHistory } from 'vue-router';


import ProductList from './components/ProductList.vue';
import ProductDetails from './components/ProductDetails.vue';
import AddProduct from './components/AddProduct.vue';
import EditProduct from './components/EditProduct.vue';
import Login from './components/Login.vue'; // Import the Login component
import Register from './components/Register.vue'; // Import the Register component


const routes = [
{
path: '/',
name: 'ProductList',
component: ProductList,
meta: { public: false } // Mark this route as private
},
{
path: '/product/:id',
name: 'ProductDetails',
component: ProductDetails,
meta: { public: false } // Mark this route as private
},
{
path: '/add-product',
name: 'AddProduct',
component: AddProduct,
meta: { public: false } // Mark this route as private
},
{
path: '/edit-product/:id',
name: 'EditProduct',
component: EditProduct,
meta: { public: false } // Mark this route as private
},
{
path: '/login', // Add this route
name: 'Login',
component: Login,
meta: { public: true } // Mark this route as public
},
{
path: '/register', // Add this route
name: 'Register',
component: Register,
meta: { public: true } // Mark this route as public
}
];


const router = createRouter({
history: createWebHistory(),
routes,
});



router.beforeEach((to, from, next) => {
// Check if the user is authenticated (e.g., check if a token is stored)
const isAuthenticated = !!localStorage.getItem('token');


if (!to.meta.public && !isAuthenticated) {
// If the route is not public and the user is not authenticated, redirect to login
next({ name: 'Login' });
} else {
// Otherwise, allow navigation
next();
}
});


export default router;

Now, if you log out and try to access the private routes directly from the address bar, you should be redirected to the login page.

Step 6: Create a Store.js file

Create a central Vuex store to handle the authentication state of your application. This will allow you to check whether the user is logged in from any component, including your Navbar.

Before you create the store.js file, you need to install Vuex in your project. Vuex is the official state management library for Vue.js.

If you are using npm, you can install Vuex by running the following command in your terminal:

npm install vuex@next

For those using yarn:

yarn add vuex@next

Once Vuex is installed, you can create a new file store.js and include the content below.

`.src/store.js`

// Importing required modules
import { createStore } from 'vuex' // Vuex's createStore function to create a store
import router from './router'; // Importing router to control routing in actions


// Creating a Vuex store
export default createStore({
// State object contains all the reactive data that we want to share across components
state: {
// Initialize isLoggedIn from localStorage to persist login status across page refreshes
isLoggedIn: !!localStorage.getItem('token')
},
// Mutations are functions that directly mutate the state.
// Each mutation handler gets the entire state tree as the first argument.
mutations: {
// Mutation to set isLoggedIn to true
LOGIN(state) {
state.isLoggedIn = true
},
// Mutation to set isLoggedIn to false
LOGOUT(state) {
state.isLoggedIn = false
}
},
// Actions are functions that cause side effects and can involve
// asynchronous operations. Actions can also commit mutations.
actions: {
// Login action that commits LOGIN mutation
login({ commit }) {
// login logic here, then:
commit('LOGIN')
},
// Logout action that commits LOGOUT mutation and dispatches navigateToLogin action
logout({ commit, dispatch }) {
commit('LOGOUT');
dispatch('navigateToLogin');
},
// Action to navigate to login route using Vue Router
navigateToLogin() {
router.push({ name: 'Login' });
}
}
})

The store.js file starts by importing createStore from vuex and router from the router.js file. createStore is a function that’s used to create a new Vuex store, and the router is used to control routing in certain actions.

The createStore function is called with an object that defines the state, mutations, and actions for the Vuex store.

The state object contains all the reactive data that you want to share across components. In this case, there’s a single state variable isLoggedIn, which is initialized from localStorage to persist login status across page refreshes.

mutations are functions that directly mutate the state. In this case, there are two mutation functions LOGIN and LOGOUT, which set isLoggedIn to true and false respectively.

actions are functions that cause side effects and can involve asynchronous operations. Actions can also commit mutations. In this case, there are three actions defined: login, logout, and navigateToLogin. The login action commits the LOGIN mutation. The logout action commits the LOGOUT mutation and dispatches the navigateToLogin action. The navigateToLogin action uses the router to navigate to the login route.

Step 7: Create a Navbar file

Now we need to create a navbar vue file to navigate into the various routes we have created.

src/components/Navbar.vue

<template>
<!-- Navigation bar -->
<nav>
<!-- Links for different routes. They are conditionally rendered based on the 'isLoggedIn' state. -->
<router-link to="/" v-if="isLoggedIn">Product List</router-link> |
<router-link to="/add-product" v-if="isLoggedIn">Add Product</router-link> |
<router-link to="/login" v-if="!isLoggedIn">Login</router-link> |
<router-link to="/register" v-if="!isLoggedIn">Register</router-link> |
<!-- Logout button that calls 'logout' action when clicked -->
<a href="#" @click="logout" v-if="isLoggedIn">Logout</a>
</nav>
</template>


<script>
import { mapState, mapActions } from 'vuex'


export default {
computed: {
// Using Vuex helpers to map the 'isLoggedIn' state to a computed property
...mapState(['isLoggedIn'])
},
methods: {
// Using Vuex helpers to map the 'logout' action to a method
...mapActions(['logout'])
}
}
</script>
To make certain links hidden when the user is logged out, you can add a condition to check if the user is logged in using the isLoggedIn state from Vuex. I have used the v-if directive to conditionally render these links.
In this updated version of your code, the “Product List” and “Add Product” links will only be visible when the user is logged in (v-if="isLoggedIn"), while the “Login” and “Register” links will only be visible when the user is not logged in (v-if="!isLoggedIn").
In the script setup, mapState is a helper that creates computed properties that return the state values from the store, and mapActions is a helper that maps the store actions to methods in your component. The isLoggedIn property will now reactively update across your application when it changes in the store.
we need to update the main.js as follows…

Step 8: Update Main Js and App File

`src/main.js`
// Importing required modules and components
import { createApp } from 'vue';
import App from './App.vue';
import store from './store'; // Vuex store
import router from './router.js'; // Vue router


// Creating a Vue application instance
const app = createApp(App)


// Registering Vuex store and Vue Router with the application
app.use(store)
app.use(router)


// Mounting the application to an HTML element with id 'app'
app.mount('#app')

Lastly, Update the app. vue file as follows…

`src/App.vue`
<template>
<!-- Importing and using Navbar component -->
<Navbar />
<div id="app">
<!-- Insertion point for views based on the current route -->
<!-- When navigating to different URLs, different content will be displayed here -->
<router-view/>
</div>
</template>


<script>
// Importing Navbar component
import Navbar from './components/Navbar.vue';


export default {
// Registering Navbar component
components: {
Navbar,
},
// Name of the root component
name: 'App',
}
</script>



<style>
#app {
text-align: center;
margin-top: 60px;
}
</style>

If you want to completely ignore ESLint in your Vue.js application, you can do so by editing your vue.config.js file. If you don’t have a vue.config.js file in the root of your project, create one.

Add the following configuration to the vue.config.js file:

module.exports = {
    lintOnSave: false,
};

Step 9: Add Product Component

The Add product component is responsible for inserting products into our Laravel backend. I have commented out the code for better understanding.

<template>
<!-- Form for adding a product -->
<!-- @submit.prevent="submitForm" prevents the default form submission and calls the 'submitForm' method instead -->
<form @submit.prevent="submitForm" class="add-product-form">
<!-- Input for the product name -->
<!-- v-model="name" binds the input to the 'name' data property -->
<input class="input-field" v-model="name" placeholder="Product Name" />
<!-- Error message for the product name -->
<!-- Displayed if 'errors.name' is truthy -->
<p v-if="errors.name" class="error">{{ errors.name }}</p>


<!-- Input for the product description -->
<!-- v-model="description" binds the input to the 'description' data property -->
<input class="input-field" v-model="description" placeholder="Product Description" />
<!-- Error message for the product description -->
<!-- Displayed if 'errors.description' is truthy -->
<p v-if="errors.description" class="error">{{ errors.description }}</p>


<!-- Input for the product price -->
<!-- v-model="price" binds the input to the 'price' data property -->
<input class="input-field" v-model="price" placeholder="Product Price" />
<!-- Error message for the product price -->
<!-- Displayed if 'errors.price' is truthy -->
<p v-if="errors.price" class="error">{{ errors.price }}</p>


<!-- Button for submitting the form -->
<button type="submit" class="submit-button">Add Product</button>
</form>
</template>


<script>
// Importing the axios instance
import axios from '@/axios';


export default {
// Component data
data() {
return {
// Product details
name: '',
description: '',
price: '',
// Validation errors
errors: {}
};
},
methods: {
// Validate the input fields
validateInput() {
// Errors object
const errors = {};
// Validate the product name
if (!this.name) errors.name = 'Name is required';
// Validate the product description
if (!this.description) errors.description = 'Description is required';
// Validate the product price
if (!this.price || isNaN(this.price)) errors.price = 'Price is required and must be a number';
return errors;
},
// Handle form submission
async submitForm() {
// Validate the input fields
const errors = this.validateInput();
// If there are validation errors, update the 'errors' data property and stop execution
if (Object.keys(errors).length > 0) {
this.errors = errors;
return;
}


try {
// Send a POST request to the API to add the product
await axios.post('/products', {
name: this.name,
description: this.description,
price: this.price
});
// If the request is successful, redirect to the product list
this.$router.push('/');
} catch (error) {
// If an error occurs, log it to the console
console.error("An error occurred while adding the product:", error);
}
}
}
};
</script>






<style scoped>
.error {
color: red;
}
.add-product-form {
max-width: 400px;
margin: 20px auto;
padding: 20px;
background-color: #f9f9f9;
border-radius: 8px;
}


.input-field {
display: block;
width: 100%;
margin: 10px 0;
padding: 10px;
font-size: 1em;
}


.submit-button {
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}


.submit-button:hover {
background-color: #45a049;
}
</style>

Step 10: Product List Component

The product list component is responsible for displaying all our products from the Laravel 10 API. The codes have comments.

<template>
<!-- Product list container -->
<div class="product-list">
<ul>
<!-- Product list item -->
<!-- Use Vue's 'v-for' directive to loop through the products array -->
<li class="product-item" v-for="product in products" :key="product.id">
<!-- Display the product name -->
<span class="product-name">{{ product.name }}</span>

<!-- Container for action links -->
<div class="action-links">
<!-- Link to edit the product -->
<!-- Use Vue Router's 'router-link' component to create a navigation link -->
<!-- Use Vue's 'v-bind' directive (shorthand ':') to bind the 'to' prop with route data -->
<router-link class="edit-link" :to="{ name: 'EditProduct', params: { id: product.id } }">Edit</router-link>

<!-- Link to view product details -->
<router-link class="details-link" :to="{ name: 'ProductDetails', params: { id: product.id } }">View Details</router-link>

<!-- Button to delete the product -->
<!-- Use Vue's 'v-on' directive (shorthand '@') to bind the click event with the 'deleteProduct' method -->
<button class="delete-button" @click="deleteProduct(product.id)">Delete</button>
</div>
</li>
</ul>
</div>
</template>

<script>
import axios from '@/axios'; // Import the axios instance

export default {
// Component data
data() {
return {
// Initialize products as an empty array
products: []
};
},
methods: {
// Method to delete a product
async deleteProduct(id) {
try {
// Make a DELETE request to the API
await axios.delete(`/products/${id}`);

// If the request is successful, filter the deleted product out of the products array
this.products = this.products.filter(product => product.id !== id);
} catch (error) {
// If an error occurs, log it to the console
console.error("An error occurred while deleting the product:", error);
}
}
},
// Lifecycle hook that is called after the instance has been created
async created() {
try {
// When the component is created, fetch the products from the API
const response = await axios.get('http://localhost:8000/api/products');
// Update the products array with the data from the API
this.products = response.data;
} catch (error) {
// If an error occurs, log it to the console
console.error("An error occurred while fetching the products:", error);
}
}
}
</script>

<style scoped>
.product-list {
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f9f9f9;
border-radius: 8px;
}

.product-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
margin: 8px 0;
border-bottom: 1px solid #ddd;
}

.product-name {
font-weight: bold;
font-size: 1.1em;
}

.action-links {
display: flex;
align-items: center;
}

.edit-link, .details-link, .delete-button {
margin: 0 8px;
font-size: 0.9em;
}

.edit-link, .details-link {
text-decoration: none;
color: #337ab7;
}

.delete-button {
padding: 5px 10px;
background-color: #f44336;
color: #fff;
border: none;
border-radius: 4px;
}

.delete-button:hover {
background-color: #d32f2f;
cursor: pointer;
}
</style>

Step 11: Edit Product Component

This component will handle edits for our products.

<template>
<!-- The component markup -->
<div class="edit-product">
<!-- The form for editing a product. The 'submit' event is prevented and instead the 'submitForm' method is called -->
<form @submit.prevent="submitForm" class="edit-product-form">
<!-- Various input fields bound to product properties with error handling -->
<input class="input-field" v-model="product.name" placeholder="Product Name" />
<p v-if="errors.name" class="error">{{ errors.name }}</p>

<input class="input-field" v-model="product.description" placeholder="Product Description" />
<p v-if="errors.description" class="error">{{ errors.description }}</p>

<input class="input-field" v-model="product.price" placeholder="Product Price" />
<p v-if="errors.price" class="error">{{ errors.price }}</p>

<!-- Submit button -->
<button type="submit" class="submit-button">Update Product</button>
</form>
</div>
</template>

<script>
import { ref, onMounted } from 'vue'; // Importing necessary functionalities from Vue 3 Composition API
import { useRoute, useRouter } from 'vue-router'; // Importing routing functionalities
import axios from '@/axios'; // Importing axios for making HTTP requests

export default {
setup() {
// Creating a reactive reference to the product object and error messages
const product = ref({ id: null, name: '', description: '', price: '' });
const errors = ref({});

// Getting the current route instance and extracting product id from it
const route = useRoute();
product.value.id = route.params.id;

// Getting the router instance
const router = useRouter();

// Defining input validation function
const validateInput = () => {
const errorMessages = {};
if (!product.value.name) errorMessages.name = 'Name is required';
if (!product.value.description) errorMessages.description = 'Description is required';
if (!product.value.price || isNaN(product.value.price)) errorMessages.price = 'Price is required and must be a number';
return errorMessages;
};

// Defining form submission function
const submitForm = async () => {
const errorMessages = validateInput();
if (Object.keys(errorMessages).length > 0) {
errors.value = errorMessages;
return;
}

// Sending updated product data to the server
try {
await axios.put(`/products/${product.value.id}`, product.value);
// Upon successful update, redirecting to the Product List page
router.push('/');
} catch (error) {
console.error("An error occurred while updating the product:", error);
if (error.response && error.response.status === 422) {
// Handling server-side validation errors
errors.value = error.response.data.errors;
}
}
};

// Fetching product data from the server when the component is mounted
onMounted(async () => {
try {
const response = await axios.get(`/products/${product.value.id}`);
product.value.name = response.data.name;
product.value.description = response.data.description;
product.value.price = response.data.price;
} catch (error) {
console.error("An error occurred while fetching the product:", error);
}
});

// Exposing the reactive references and methods to be used within the template
return { product, submitForm, errors };
}
};
</script>


<style scoped>
.error {
color: red;
}
.edit-product {
max-width: 400px;
margin: 20px auto;
padding: 20px;
background-color: #f9f9f9;
border-radius: 8px;
}

.input-field {
display: block;
width: 100%;
margin: 10px 0;
padding: 10px;
font-size: 1em;
}

.submit-button {
padding: 10px 20px;
background-color: #FF9800;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}

.submit-button:hover {
background-color: #e68a00;
}
</style>

Step 12: Product Details Component

The product details component is responsible for showing a single product’s detail.

<template>
<!-- Product details container -->
<!-- Use Vue's 'v-if' directive to render this container only if 'product' is truthy -->
<div v-if="product" class="product-details">
<!-- Display the product name -->
<h2 class="product-title">{{ product.name }}</h2>
<!-- Display the product description -->
<p class="product-description">{{ product.description }}</p>
<!-- Display the product price -->
<p class="product-price">Price: ${{ product.price }}</p>
</div>
</template>

<script>
import axios from '@/axios'; // Import the axios instance

export default {
// Component data
data() {
return {
// Initialize product as null
product: null
};
},
// Lifecycle hook that is called after the instance has been mounted
async mounted() {
// Get the product ID from the route params
const productId = this.$route.params.id;
try {
// Make a GET request to the API to fetch the product data
const response = await axios.get(`/products/${productId}`);
// Update the product data with the data from the API
this.product = response.data;
} catch (error) {
// If an error occurs, log it to the console
console.error("An error occurred while fetching the product:", error);
}
}
};
</script>

<style scoped>
.product-details {
max-width: 600px;
margin: 20px auto;
padding: 20px;
background-color: #f9f9f9;
border-radius: 8px;
}

.product-title {
margin-bottom: 20px;
font-size: 2em;
}

.product-description {
font-size: 1.2em;
}

.product-price {
margin-top: 20px;
font-size: 1.2em;
font-weight: bold;
}
</style>

Watch It on my YouTube channel:

Sign up for free tutorials in your inbox.

We don’t spam! Read our privacy policy for more info.

6 thoughts on “How To Create A Laravel 10 and Vue Js 3 E-commerce App”

  1. Hi,

    The code of the api.php on your youtube video is not the same as your code here in the website article.

    Do you have a github of this full project?

    Thanks

Leave a Comment

Your email address will not be published. Required fields are marked *