Laravel Projects Examples

Laravel Inertia Vue Roles Permissions with Spatie package

This project demonstrates how to add roles and permissions with spatie/laravel-permission package to your Inertia with Vue.js application.

How to install

  • Clone the repository with git clone
  • Copy the .env.example file to .env and edit database credentials there
  • Run composer install
  • Run php artisan key:generate
  • Run php artisan storage:link
  • Run php artisan migrate --seed (it has some seeded data for your testing)
  • Run npm ci and npm run build
  • Launch the main URL /. Log in with credentials for a desired user:
  • That's it.

How It Works

In this project, we use the spatie/laravel-permission package to manage roles with permissions.

In the backend, we check permissions using Gates in the controller.

app/Http/Controllers/TaskController.php:

use App\Http\Requests\StoreTaskRequest;
use App\Http\Requests\UpdateTaskRequest;
use App\Models\Task;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Gate;
use Inertia\Inertia;
use Inertia\Response;
 
class TaskController extends Controller
{
public function index(): Response
{
$tasks = Task::all();
 
return Inertia::render('Tasks/Index', compact('tasks'));
}
 
public function create(): Response
{
Gate::authorize('task_create');
 
return Inertia::render('Tasks/Create');
}
 
public function store(StoreTaskRequest $request): RedirectResponse
{
Gate::authorize('task_create');
 
Task::create($request->validated());
 
return redirect()->route('tasks.index');
}
 
public function edit(Task $task): Response
{
Gate::authorize('task_edit');
 
return Inertia::render('Tasks/Edit', compact('task'));
}
 
public function update(UpdateTaskRequest $request, Task $task): RedirectResponse
{
Gate::authorize('task_edit');
 
$task->update($request->validated());
 
return redirect()->route('tasks.index');
}
 
public function destroy(Task $task): RedirectResponse
{
Gate::authorize('task_destroy');
 
$task->delete();
 
return redirect()->route('tasks.index');
}
}

We pass a list of user permissions to the front end using the Inertia shared data feature in the HandleInertiaRequests Middleware.

The list returns an array with the user's permissions and true value if he has that permission.

app/Http/Middleware/HandleInertiaRequests.php:

use Illuminate\Http\Request;
use Inertia\Middleware;
use Spatie\Permission\Models\Permission;
 
class HandleInertiaRequests extends Middleware
{
// ...
 
public function share(Request $request): array
{
return [
...parent::share($request),
'auth' => [
'user' => $request->user(),
'can' => $request->user()?->getPermissionsViaRoles()
->map(function (Permission $permission): array {
return [$permission['name'] => auth()->user()->can($permission['name'])];
})
->collapse()
->all(),
],
];
}
}

In the Vue component, we can access shared data from global props. Then, in the template, we show elements using v-if and checking for permission.

<script setup>
import PrimaryButton from '@/Components/PrimaryButton.vue';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
import { Head, Link, router, usePage } from '@inertiajs/vue3';
import { computed } from 'vue';
 
const page = usePage();
 
const can = computed(() => page.props.auth.can);
 
defineProps({
tasks: Object,
});
 
function destroy(id) {
router.delete(route('tasks.destroy', id), {
onBefore: () => confirm('Are you sure you want to delete?'),
});
}
</script>
 
<template>
<Head title="Tasks" />
 
<AuthenticatedLayout>
<template #header>
<h2 class="text-xl font-semibold leading-tight text-gray-800">
Tasks List
</h2>
</template>
 
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="overflow-hidden bg-white shadow-sm sm:rounded-lg">
<div class="border-b border-gray-200 bg-white p-6">
<div class="mb-4" v-if="can.task_create">
<Link
:href="route('tasks.create')"
class="rounded-md border border-transparent bg-green-500 px-4 py-2 text-xs font-bold uppercase tracking-widest text-white hover:bg-green-700"
>
Create
</Link>
</div>
 
<div class="min-w-full align-middle">
<table
class="min-w-full divide-y divide-gray-200 border"
>
<thead>
<tr>
<th
class="w-10 bg-gray-50 px-6 py-3 text-left"
>
<span
class="text-xs font-medium uppercase leading-4 tracking-wider text-gray-500"
>ID</span
>
</th>
<th
class="bg-gray-50 px-6 py-3 text-left"
>
<span
class="w-full text-xs font-medium uppercase leading-4 tracking-wider text-gray-500"
>Description</span
>
</th>
<th
class="w-48 bg-gray-50 px-6 py-3 text-left"
></th>
</tr>
</thead>
 
<tbody
class="divide-y divide-solid divide-gray-200 bg-white"
>
<tr
class="bg-white"
v-for="task in tasks"
:key="task.id"
>
<td
class="whitespace-no-wrap px-6 py-4 text-sm leading-5 text-gray-900"
>
{{ task.id }}
</td>
<td
class="whitespace-no-wrap px-6 py-4 text-sm leading-5 text-gray-900"
>
{{ task.description }}
</td>
<td class="space-x-2">
<Link
v-if="can.task_edit"
:href="
route('tasks.edit', task)
"
class="rounded-md border border-transparent bg-green-500 px-4 py-2 text-xs font-bold uppercase tracking-widest text-white hover:bg-green-700"
>
Edit
</Link>
<PrimaryButton
v-if="can.task_destroy"
@click="destroy(task.id)"
>
Delete
</PrimaryButton>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
</template>