Laravel 9 VueJs SPA CRUD with Example

Published on May 15, 2022

Hello Artisans, today we'll discuss about SPA (single page application) CRUD with Laravel & VueJs. VueJs is one of the most popular frontend framework nowadays. It's a very small framework with a massive performance. For more you can check here So, no more talk, let's see how we can easily crate a simple SPA CRUD application using Laravel & VueJs.

Note: Tested on Laravel 9.11

Table of Contents

  1. Install and Configure Vue and it's Dependency
  2. Create and Setup Controller
  3. Create and Setup Component
  4. Define Routes
  5. Setup blade File
  6. Output

Install and Configure Vue and it's Dependency

First of all, we'll install the npm through the below command

npm i

Then we'll install the vue 2.6.14. Because as for now it's on of the most stable one. So, fire the below command in the terminal.

npm i [email protected]

Now, we'll install the laravel-vue-pagination package. It's quite easy to use pagination in our list. Fire the below command to install it.

npm install [email protected]

Now fire the below command to install the vue-template-complier and vue-loader. So that our mixin and webpack can recognize the .vue file.

npm install vue-template-compiler [email protected]^15.9.7 --save-dev --legacy-peer-deps

Now we'll install the sweetalert2. So that we can notify the user through toastr notification.

npm i sweetalert2

Now we installed all the necessary packages. Now we need to setup our app.js file. So, open the file and replace it with below codes.

resources/js/app.js
require('./bootstrap');

import Vue from 'vue/dist/vue'

//Pagination laravel-vue-pagination
Vue.component('pagination', require('laravel-vue-pagination'));

import Swal from 'sweetalert2'
window.Swal = require('sweetalert2')

const Toast = Swal.mixin({
    toast: true,
    position: 'top-end',
    showConfirmButton: false,
    timer: 3000,
    timerProgressBar: true
});

window.Toast = Toast;

let Fire = new Vue()
window.Fire = Fire;

Vue.component('users', require('./components/users.vue').default);

const app = new Vue({
    el: '#app',
});

Now we need to configure webpack.mix.js, so that we can mix our js and css file. So, open the file and replace it with below codes.

webpack.mix.js
const mix = require('laravel-mix');

mix.js('resources/js/app.js', 'public/js')
    .postCss('resources/css/app.css', 'public/css', [
        //
    ]).vue();

And finally run the below command to watch your developments as well as generate the mix files.

npm run watch

And that's it. We're done with our vuejs setup.

Create and Setup Controller

First of all, create a controller so that we can write our logics or query to show the result. So, fire the below commands in the terminal.

 php artisan make:controller UserController

It'll create a controller under app\Http\Controllers called UserController.php. Open the file and replace with below codes.

app/Http/Controllers/UserController.php
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class UserController extends Controller
{
    public function index(): \Illuminate\Http\JsonResponse
    {
        return response()->json([
            'users' => User::latest()->paginate(10)
        ], 200);
    }

    public function store(Request $request): \Illuminate\Http\JsonResponse
    {
        $request->validate([
            'name' => 'required',
            'email' => 'required|email',
            'password' => 'required|min:8',
        ]);

        DB::beginTransaction();
        $request['password'] = bcrypt($request->password);
        User::create($request->all());
        DB::commit();
        return response()->json(['success' => 'User Created Successfully'], 201);

    }


    public function update(Request $request, $id): \Illuminate\Http\JsonResponse
    {
        $request->validate([
            'name' => 'required',
            'email' => 'required|email',
            'password' => 'required|min:8',
        ]);

        $user = User::find($request->id);
        $request['password'] = bcrypt($request->password);
        $user->update($request->all());

        return response()->json(['success' => 'User Updated Successfully'], 200);
    }

    public function destroy($id): \Illuminate\Http\JsonResponse
    {
        User::destroy($id);

        return response()->json([
            'success' => 'User Removed Successfully',
        ]);
    }

}

Create and Setup Component

Now we need to create a vue component where we'll fetch our user and perform read/write operation. So, create a file under resources/js/components named users.vue and replace with following codes.

resources/js/components/users.vue
<template>
    <div class="container mt-5">
        <div class="row">
            <div class="col-md-12">
                <h4 class="mb-5">Laravel Vue SPA - shouts.dev</h4>
                <table id="table" class="table table-bordered table-striped">
                    <thead>
                    <tr>
                        <th>Name</th>
                        <th>Email</th>
                        <th class="check">Action
                            <a data-toggle="modal" data-target="#user"
                               style="float: right;cursor: pointer; color: white; padding: 2px;"
                               @click="openModalWindow" class="btn btn-sm btn-warning py-0">
                                <i class="fa fa-plus-square"> Add User
                                </i>
                            </a>
                        </th>
                    </tr>
                    </thead>
                    <tbody>
                    <tr v-for="user in users.data" :key="user.id">
                        <td>{{ user.name }}</td>
                        <td>{{ user.email }}</td>
                        <td class="check">
                            <a title="Edit category" class="btn btn-sm btn-dark py-0"
                               style="color:white;cursor: pointer;" @click="edit(user)">Edit</a>
                            <a class="btn btn-sm btn-danger py-0" @click="deleteUser(user.id)" style="color:white;">Delete</a>
                        </td>
                    </tr>
                    </tbody>
                    <pagination :data="users" @pagination-change-page="getResults"></pagination>
                </table>
                <div class="modal fade" id="user" tabindex="-1" role="dialog" aria-labelledby="addNewLabel"
                     aria-hidden="true">
                    <div class="modal-dialog modal-dialog-centered" role="document">
                        <div class="modal-content">
                            <div class="modal-header">
                                <h5 v-if="form.id" class="modal-title">Update User</h5>
                                <h5 v-else class="modal-title">Add New User</h5>
                            </div>

                            <form @submit.prevent="form.id ? updateUser() : createUser()">
                                <div class="modal-body">
                                    <div class="form-group">
                                        <input v-model="form.name" type="text" name="name"
                                               placeholder="Name"
                                               class="input2 form-control" :class="{ 'is-invalid': errors.name }"
                                               data-validate="User name is required">
                                        <span class="focus-input2" data-placeholder="Company Name"></span>
                                        <span class="text-danger" v-if="errors.name">{{ errors.name[0] }}</span>
                                    </div>
                                    <div class="form-group">
                                        <input v-model="form.email" type="text" name="email"
                                               placeholder="Email"
                                               class="input2 form-control" :class="{ 'is-invalid': errors.email }"
                                               data-validate="User Email is required">
                                        <span class="focus-input2" data-placeholder="User Email"></span>
                                        <span class="text-danger" v-if="errors.email">{{ errors.email[0] }}</span>
                                    </div>
                                    <div class="form-group">
                                        <input v-model="form.password" type="password" name="password"
                                               placeholder="password"
                                               class="input2 form-control" :class="{ 'is-invalid': errors.password }"
                                               data-validate="Password is required">
                                        <span class="focus-input2" data-placeholder="Password"></span>
                                        <span class="text-danger" v-if="errors.password">{{ errors.password[0] }}</span>
                                    </div>
                                </div>
                                <div class="modal-footer">
                                    <button type="button" class="btn btn-danger" data-dismiss="modal">Close</button>
                                    <button v-if="form.id" type="submit" class="btn btn-success">Update</button>
                                    <button v-else type="submit" class="btn btn-success">Create</button>
                                </div>

                            </form>

                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>


export default {

    data() {
        return {
            users: {},
            errors: [],
            form: {
                id: '',
                name: '',
                email: '',
                password: '',
            },
        }
    },
    methods: {
        resetForm() {
            for (var key in this.form) {
                this.form[key] = '';
            }
            this.errors = [];
        },
        openModalWindow() {
            this.resetForm();
            $('#user').modal('show');
        },
        getUsers() {
            axios.get("/users").then(data => (this.users = data.data.users));
        },
        getResults(page = 1) {
            axios.get('/users?page=' + page)
                .then(response => {
                    this.users = response.data.users;
                });
        },
        createUser() {
            axios.post('/users', this.form)
                .then(() => {

                    Fire.$emit('load_user');

                    Toast.fire({
                        icon: 'success',
                        title: 'User created successfully'
                    })

                    $('#user').modal('hide');

                })
                .catch((error) => {
                    console.log("Error......");
                    if (error.response.status == 422) {
                        this.errors = error.response.data.errors;
                    }
                })

        },
        edit(user) {
            this.resetForm();
            $('#user').modal('show');
            this.form = user;
        },
        updateUser() {
            axios.put('/users/' + this.form.id, this.form)
                .then(() => {

                    Toast.fire({
                        icon: 'success',
                        title: 'User updated successfully'
                    })

                    Fire.$emit('load_user');

                    $('#user').modal('hide');
                })
                .catch((error) => {
                    console.log("Error.....");
                    if (error.response.status == 422) {
                        this.errors = error.response.data.errors;
                    }
                })
        },
        deleteUser(id) {
            Swal.fire({
                title: 'Are you sure?',
                text: "You won't be able to revert this!",
                icon: 'warning',
                showCancelButton: true,
                confirmButtonColor: '#3085d6',
                cancelButtonColor: '#d33',
                confirmButtonText: 'Yes, delete it!'
            }).then((result) => {

                if (result.value) {

                    axios.delete('/users/' + id)
                        .then((response) => {
                            Swal.fire(
                                'Deleted!',
                                'User deleted successfully',
                                'success'
                            )

                            Fire.$emit('load_user');

                        }).catch(() => {
                        Swal.fire({
                            icon: 'error',
                            title: 'Oops...',
                            text: 'Something went wrong!',
                        })
                    })
                }

            })
        },
    },
    created() {

        this.getUsers();

        Fire.$on('load_user', () => {
            this.getUsers();
        });

    },

}
</script>

<style scoped>
.pagination {
    margin-top: 30px;
    float: right;
}

.validation_error {
    border: 1px solid red !important;
}
</style>

Define Routes

Now we need to put the below routes in web.php. Replace it with below codes.

routes/web.php
<?php

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
});

Route::resource('users',\App\Http\Controllers\UserController::class);

Setup blade File

Now we'll modify the default blade file named welcome.blade.php which comes with Laravel by default.

resources/views/welcome.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>{{ config('app.name', 'Laravel') }}</title>
    <link href="{{ mix('css/app.css') }}" rel="stylesheet">
    <title>Laravel VUE SPA</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>

<div id="app">

    <users></users>
    <div class="py-4">
        @yield('content')
    </div>

</div>
<script src="{{ mix('js/app.js') }}" defer></script>
</body>
</html>

Output

And finally we're ready with our setup. It's time to check our output. Now go to http://127.0.0.1:8000/users, If everything goes well you'll find a below output.

Laravel & VuJs CRUD SPA

That's it for today. I hope you've enjoyed this tutorial. You can also download this tutorial from GitHub. Thanks for reading. 🙂