Share this:

Laravel 10 and Vue 3 are two widely-used web development frameworks, with Laravel being a popular PHP framework and Vue 3 being a progressive JavaScript framework. Combining the two provides a powerful solution for creating modern web applications. In this guide, we will explore how to build a Laravel 10 application with Vue 3, focusing on CRUD (Create, Read, Update, Delete) operations.

Table of Contents

  1. Prerequisites
  2. Setting up the Laravel 10 and Vue 3 Environment
  3. Building the Laravel API
    • 3.1 Creating the Model, Migration, and Controller
    • 3.2 Implementing CRUD Operations in the API
  4. Integrating Vue 3 with Laravel
    • 4.1 Installing Vue 3 and Dependencies
    • 4.2 Setting up the Vue 3 Components
  5. Implementing CRUD Operations in Vue 3
  6. Testing
  7. Conclusion

Prerequisites

Before we begin, ensure that you have the following software installed on your machine:

  • PHP 8.0 or higher
  • Composer
  • Laravel 10
  • Node.js and npm
  • Text editor or IDE of your choice

Setting up the Laravel 10 and Vue 3 Environment

First, we need to create a new Laravel project using the following command:

composer create-project --prefer-dist laravel/laravel laravel-vue-crud

Navigate to the project directory:

cd laravel-vue-crud

Next, we will install the Laravel frontend scaffolding. This provides a starting point for integrating Vue.js into our application. To install it, run:

composer require laravel/ui

Now, generate the basic scaffolding for Vue.js:

php artisan ui vue

Finally, install the necessary npm dependencies:

npm install

Building the Laravel API

Creating the Model, Migration, and Controller

For this guide, we will create a simple model named Product with three fields: name, description, and price. To create the model, migration, and controller files, run the following command:

php artisan make:model Product -mcr

Open the newly created migration file in the database/migrations folder. Modify the up() method to include the necessary fields for our Product model:

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

Now, run the migration to create the products table in the database:

php artisan migrate

Please note that before migrate you need to create a database and enter the database details to .env file.

Implementing CRUD Operations in the API

First, You need to add the name, description, and price attributes to the fillable property in your Product model. This will allow these attributes to be mass-assigned when creating or updating a product.

Open the Product model located at app/Models/Product.php and update the fillable property:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    use HasFactory;

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

Open the ProductController.php file located in the app/Http/Controllers folder. We will implement the CRUD operations within this controller. First, import the Product model:

use App\Models\Product;

Next, create the following methods within the ProductController class:

  • index() – Retrieves all products
  • store() – Adds a new product
  • show() – Retrieves a specific product
  • update() – Updates a product
  • destroy() – Deletes a product

Here is the complete code for the ProductController class:

<?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 Product::all();
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create()
    {
        
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        $request->validate([
            'name' => 'required',
            'description' => 'required',
            'price' => 'required'
        ]);
    
        return Product::create($request->all());
    }

    /**
     * Display the specified resource.
     */
    public function show(Product $product)
    {
        return $product;
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(Product $product)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, Product $product)
    {
        $request->validate([
            'name' => 'required',
            'description' => 'required',
            'price' => 'required'
        ]);
    
        $product->update($request->all());
    
        return $product;
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(Product $product)
    {
        $product->delete();
        
        return response()->json(['message' => 'Product deleted successfully']);
    }
}

Now, let’s create the API routes for these methods. Open the api.php file in the routes folder and add the following code:

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

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

Integrating Vue 3 with Laravel

Installing Vue 3 and Dependencies

Before we can start building our Vue.js components, we need to install some additional dependencies:

npm install vue@next vue-router@next axios --save

Setting up the Vue 3 Components

First, create a new folder named components inside the resources/js folder. Inside the components folder, create the following files:

  • ‘App.vue’ – The root component
  • ‘ProductList.vue’ – Component to display a list of products
  • ‘ProductForm.vue’ – Component to create and edit products
  • ‘Product.vue’ – Component to display a single product

Now, let’s update the resources/js/app.js file to include the necessary Vue.js components and set up the Vue Router:

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

// Import components
import App from './components/App.vue';
import ProductList from './components/ProductList.vue';
import ProductForm from './components/ProductForm.vue';
import Product from './components/Product.vue';

const router = createRouter({
    history: createWebHistory(),
    routes: [
        { path: '/', component: ProductList },
        { path: '/products/create', component: ProductForm },
        { path: '/products/:id', component: Product },
        { path: '/products/:id/edit', component: ProductForm },
    ]
});

const app = createApp(App);
app.use(router);
app.mount('#app');

Implementing CRUD Operations in Vue 3

Now update the code for all the file as follow:

resources/js/components/App.vue

<template>

<div class="container">
  <header>
    <div class="px-3 py-2 bg-dark text-white">
      <div class="container">
        <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
          <router-link to="/" class="d-flex align-items-center my-2 my-lg-0 me-lg-auto text-white text-decoration-none">
            <img src="https://cdn.laraveltuts.com/wp-content/uploads/2022/06/logo-dark.webp" style="height:60px;"/>
          </router-link>
          <ul class="nav col-12 col-lg-auto my-2 justify-content-center my-md-0 text-small">
            <li>
                <router-link to="/" class="nav-link text-white" style="text-decoration: none; color: white;"><svg class="bi d-block mx-auto mb-1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M4.5 21q-.625 0-1.063-.438T3 19.5v-1.9l4-3.55V21H4.5ZM8 21v-4h8v4H8Zm9 0v-8.2L12.725 9l3.025-2.675l4.75 4.225q.25.225.375.513t.125.612V19.5q0 .625-.438 1.063T19.5 21H17ZM3 16.25v-4.575q0-.325.125-.625t.375-.5L11 3.9q.2-.2.463-.287T12 3.525q.275 0 .537.088T13 3.9l2 1.775L3 16.25Z"/></svg>Home</router-link>
            </li>
            <li>
                <router-link to="/products/create" class="nav-link text-white" style="text-decoration: none; color: white;"><svg class="bi d-block mx-auto mb-1" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M7 9h10V7H7v2Zm11 14q-2.075 0-3.538-1.463T13 18q0-2.075 1.463-3.538T18 13q2.075 0 3.538 1.463T23 18q0 2.075-1.463 3.538T18 23Zm-.5-2h1v-2.5H21v-1h-2.5V15h-1v2.5H15v1h2.5V21ZM3 21V3h18v8.7q-.725-.35-1.463-.525T18 11q-.275 0-.513.013t-.487.062V11H7v2h6.1q-.425.425-.787.925T11.675 15H7v2h4.075q-.05.25-.063.488T11 18q0 .825.15 1.538T11.675 21H3Z"/></svg>Add Product</router-link>
            </li>
          </ul>
        </div>
      </div>
    </div>
  </header>

  <div class="card">
    <div class="card-body">
      <div class="container-fluid">
        <router-view></router-view>
      </div>
    </div>
  </div>

</div>

</template>

<script>
export default {
  name: 'App'
}
</script>

resources/js/components/Product.vue

<template>
    <div>
        <h5 id="associating-form-text-with-form-controls">Name:</h5>
        <h6>{{ product.name }}</h6>
        <h5 id="associating-form-text-with-form-controls">Description:</h5>
        <p>{{ product.description }}</p>
        <h5 id="associating-form-text-with-form-controls">Price:</h5>
        <p>Price: {{ product.price }}</p>
        <router-link :to="`/products/${product.id}/edit`" class="btn btn-primary">Edit</router-link>
    </div>
</template>

<script>
import axios from 'axios';

export default {
data() {
    return {
    product: {}
    }
},
async created() {
    try {
    const response = await axios.get(`/api/products/${this.$route.params.id}`);
    this.product = response.data;
    } catch (error) {
    console.error(error);
    }
}
}
</script>

resources/js/components/ProductForm.vue

<template>
  <div>
    <h2 v-if="isNewProduct">Add Product</h2>
    <h2 v-else>Edit Product</h2>
      <form @submit.prevent="submitForm">
        <div class="mb-3">
          <label for="name" class="form-label">Name:</label>
          <input class="form-control" type="text" id="name" v-model="product.name" required />
        </div>
        <div class="mb-3">
          <label for="description" class="form-label">Description:</label>
          <textarea class="form-control" id="description" v-model="product.description" required></textarea>
        </div>
        <div class="mb-3">
          <label for="price" class="form-label">Price:</label>
          <input class="form-control" type="number" id="price" v-model="product.price" required />
        </div>
        <button type="submit" v-if="isNewProduct" class="btn btn-primary">Add Product</button>
        <button type="submit" v-else class="btn btn-primary">Update Product</button>
      </form>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      product: {
        name: '',
        description: '',
        price: 0
      }
    }
  },
  computed: {
    isNewProduct() {
      return !this.$route.path.includes('edit');
    }
  },
  async created() {
    if (!this.isNewProduct) {
      const response = await axios.get(`/api/products/${this.$route.params.id}`);
      this.product = response.data;
    }
  },
  methods: {
    async submitForm() {
      try {
        if (this.isNewProduct) {
          await axios.post('/api/products', this.product);
        } else {
          await axios.put(`/api/products/${this.$route.params.id}`, this.product);
        }
        this.$router.push('/');
      } catch (error) {
        console.error(error);
      }
    }
  }
}
</script>

resources/js/components/ProductList.vue

<template>
    <div>
        <table class="table">
            <thead>
              <tr>
                <th scope="col">#</th>
                <th scope="col">Name</th>
                <th scope="col">Description</th>
                <th scope="col">Price</th>
                <th scope="col">Actions</th>
              </tr>
            </thead>
            <tbody>
                <tr v-for="product in products" :key="product.id">
                    <td>{{ product.id }}</td>
                    <td>{{ product.name }}</td>
                    <td>{{ product.description }}</td>
                    <td>{{ product.price }}</td>
                    <td>
                      <div class="row gap-3">
                        <router-link :to="`/products/${product.id}`" class="p-2 col border btn btn-primary">View</router-link>
                        <router-link :to="`/products/${product.id}/edit`" class="p-2 col border btn btn-success">Edit</router-link>
                        <button @click="deleteProduct(product.id)" type="button" class="p-2 col border btn btn-danger">Delete</button>
                      </div>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      products: []
    }
  },
  async created() {
    try {
      const response = await axios.get('/api/products');
      this.products = response.data;
    } catch (error) {
      console.error(error);
    }
  },
  methods: {
    async deleteProduct(id) {
      try {
        await axios.delete(`/api/products/${id}`);
        this.products = this.products.filter(product => product.id !== id);
      } catch (error) {
        console.error(error);
      }
    }
  }
}
</script>

Also update the welcome.blade.php file.

resources/views/welcome.blade.php

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Laravel 10 and Vue 3 CRUD Application - LaravelTuts.com</title>
    <!-- Fonts -->
    <link rel="stylesheet" href="https://fonts.bunny.net/css2?family=Nunito:wght@400;600;700&display=swap">
    <link href="https://getbootstrap.com/docs/5.0/dist/css/bootstrap.min.css" rel="stylesheet">
    @vite(['resources/js/app.js'])
</head>
<body>
    <div id="app"></div>
</body>
</html>

Testing

To test your Laravel 10 and Vue 3 CRUD application, you can follow these steps:

  1. Start the Laravel development server:
php artisan serve
  1. In another terminal, compile your Vue 3 assets and watch for changes:
npm run watch
  1. Open your browser and navigate to the Laravel development server URL, usually http://127.0.0.1:8000.
  2. Test each CRUD operation:a. Create: Click the “Add Product” link in the navigation bar. Fill out the form with the product’s name, description, and price, then click the “Add Product” button. You should be redirected to the homepage, and the new product should appear in the product list.b. Read: Click the “View” link next to a product in the product list. You should be taken to a page displaying the product’s name, description, and price.c. Update: Click the “Edit” link next to a product in the product list. Modify the product’s name, description, and/or price, then click the “Update Product” button. You should be redirected to the homepage, and the updated product information should be displayed in the product list.d. Delete: Click the “Delete” button next to a product in the product list. The product should be removed from the list.
  3. Check the database to ensure that the changes have been properly saved.

Video Demo:

Conclusion

In this guide, we have demonstrated how to build a Laravel 10 application with Vue 3, focusing on implementing CRUD operations. By combining the power of Laravel and Vue 3, you can create modern, responsive web applications with ease.

This guide serves as a starting point for your own projects. You can further enhance the application by adding features such as pagination, search functionality, and more advanced validation. Additionally, you may want to consider using Vuex for state management and implementing authentication and authorization to secure your application.

Share this: