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
andnpm run build
- Launch the main URL
/
. Log in with credentials for a desired user:-
[email protected]
/password
for an admin user -
[email protected]
/password
for a regular 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>