In this tutorial we’ll build a data-table in a Laravel 9 app employing VILT (Vue, Inertia, Laravel, Tailwind) stack while using Composition API of Vue3. The data-table will have the field-based searching capability along with pagination and sorting.
Laravel 9.x requires a minimum PHP version of 8.0. This tutorial assumes that you are using Linux, MacOS or WSL on Windows and Node.js, Composer and PHP 8 along with required modules i.e bcmatch, sqlite, mbstring, xml, zip, gd, mcrypt are properly installed. Also make sure that SQLite is installed as we’ll be using SQLite to keep things simple and quick; you may use MySQL or PostgreSQL as you like, however make sure the corresponding PHP module is installed too.
Using Composer, enter following command in terminal to install Laravel 9;
composer create-project laravel/laravel app 9.*
Now go inside newly created app directory by executing;
cd app
If you have VSCode installed and want to use it, then enter following command to open this project in VScode;
code .
Now, add default Breeze package by executing following command;
composer require laravel/breeze:* --dev
Then, install Breeze scaffolding with Vue and Inertia functionality by executing;
php artisan breeze:install vue
The above command will also install required node modules and compile the UI assets for the first time.
Next, setup database and Laravel environment file. Here, I’m configuring SQLite database for simplicity purposes.
Being in parent directory (app), execute following command in order to create new SQLite database file;
touch database/database.sqlite
Then find .env config file in the parent directory, open it and amend DB_CONNECTION line as follows;
DB_CONNECTION = sqlite
Remove or comment out DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD lines from .env as we are using local SQLite database file.
Now enter following command in order to run the migrations:
php artisan migrate
In order to populate database quickly, let’s utilize User model and its factory which are installed by default. So, enter following command to invoke tinker;
php artisan tinker
And then enter following command to add 100 randomly generated users in database;
User::factory(100)->create()
Type ‘quit’ and press Enter to exit tinker.
Now, create a UserController by entering the following command:
php artisan make:controller UserController
The above command will create app/Http/Controllers/UserController.php file. Then put the following code inside this newly generated UserController.php;
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
use Inertia\Inertia;
class UserController extends Controller
{
public function index(Request $request)
{
$request->validate([
'direction' => ['in:asc,desc'],
'field' => ['in:id,name,email'],
]);
$query = User::query();
if(request()->has(['select','search'])){
$query->where(request('select'),'LIKE','%'.request('search').'%');
}
if(request()->has(['field','direction'])){
$query->orderBy(request('field'),request('direction'));
}
$users = $query->paginate()->through(function ($user) {
return [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
];
})->withQueryString();
return Inertia::render('Users/Index', [
'users' => $users,
'filters' => request()->all(['select','search','field','direction']),
]);
}
}
Open routes/web.php and put the following line at the top of it:
use App\Http\Controllers\UserController;
And then insert following route into this web.php:
Route::get('users', [UserController::class, 'index'])->name('users');
Next, create a new folder resources/js/Pages/Users by entering following command;
mkdir resources/js/Pages/Users
Then, create a view file Index.vue inside resources/js/Pages/Users/ folder by entering following command;
touch resources/js/Pages/Users/Index.vue
Insert following code inside Index.vue;
<template>
<div class="bg-gray-100 shadow mx-auto lg:max-w-4xl">
<div class="p-5">
<label for="select" class="ml-2">Choose a field:</label>
<select v-model="params.select" id="select" class="px-2 ml-2 text-sm rounded-lg border">
<option value="name">Name</option>
<option value="email">Email</option>
</select>
<label for="search" class="ml-2">Search here:</label>
<input id="search" type='text' v-model="params.search" class="px-2 ml-2 text-sm rounded-lg border">
</div>
<table class="w-full">
<tr class="bg-gray-900 text-white">
<th class="px-2 py-1 text-sm font-bold text-left"><span @click="sort('id')">ID ⇵</span></th>
<th class="px-2 py-1 text-sm font-bold text-left"><span @click="sort('name')">Name ⇵</span></th>
<th class="px-2 py-1 text-sm font-bold text-left"><span @click="sort('email')">Email ⇵</span></th>
</tr>
<tr v-for="(user, index) in users.data" :key="index" :class="{'bg-gray-300': index%2===0}">
<td class="px-2 py-1 text-sm text-left">{{user.id}}</td>
<td class="px-2 py-1 text-sm text-left">{{user.name}}</td>
<td class="px-2 py-1 text-sm text-left">{{user.email}}</td>
</tr>
</table>
<Pagination :links="users.links" />
</div>
</template>
<script setup>
import {watch, reactive} from 'vue';
import _ from 'lodash';
import Pagination from '@/Components/Pagination.vue';
import { router } from '@inertiajs/vue3';
const props = defineProps({
users : Object,
filters : Object,
})
const params = reactive({
select: props.filters.select,
search: props.filters.search,
field: props.filters.field,
direction: props.filters.direction,
})
const sort = (field) => {
params.field = field;
params.direction = params.direction === 'asc'? 'desc' : 'asc';
}
watch(
params,
_.throttle((new_params, old_params) => {
let p = _.pickBy(new_params)
router.get(route('users'), p, {replace:true, preserveState:true});
}, 500)
);
</script>
Now, create Pagination component inside resources/js/Components by entering following command:
touch resources/js/Components/Pagination.vue
Then insert following code inside Pagination.vue;
<template>
<div v-if="links.length > 3">
<div class="flex flex-wrap -mb-1">
<template v-for="(link, k) in links" :key="k">
<div v-if="link.url === null" class="mr-1 mb-1 px-4 py-3 text-sm leading-4 text-gray-400 border rounded" v-html="link.label" />
<Link v-else class="mr-1 mb-1 px-4 py-3 text-sm leading-4 border rounded hover:bg-indigo-400 focus:border-indigo-500 focus:text-indigo-500" :class="{ 'bg-blue-700 text-white': link.active }" :href="link.url" v-html="link.label" />
</template>
</div>
</div>
</template>
<script setup>
import { Link } from '@inertiajs/vue3'
defineProps({
links: Array,
});
</script>
Finally, enter following command to compile client-side UI assets;
npm run build
Then, run PHP development server by running following command;
php artisan serv
Now, enter following URL in browser and test the data-table functionalities i.e., pagination, sorting and field-based search;
localhost:8000/users
This data-table is bare-bones just to give an idea how data-tables can be constructed using Vue3 Composition API in a Laravel 9 app. For more robust functionality it’s often time saving approach that we embed some third party Vue3 Components. In next few posts we’ll discover such third party Vue3 Components which are battle-tested and will definitely make our lives easier as Vue3 developers.
1 thought on “Data-table using Vue3 Composition API in Laravel 9 app”