Backend Development

Laravel Inertia React Roles Permissions CRUD

Laravel Inertia React Roles Permissions CRUD

In this post we will build CRUD pages for roles and permissions using Laravel framework and Inertia library.

 

 

Roles and permissions are essential parts on many web application dashboards. In this tutorial we will see how to build CRUD pages for roles and permissions in laravel. We will be using Inertia-js and React as our frontend along with laravel, however you can build this using Vue as well or normal blade views.

For css styling we will use tailwindcss.

 

Preparing Laravel Project

Let’s begin by creating a new laravel project using composer:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
composer create-project laravel/laravel laravel_roles_permissions_crud
composer create-project laravel/laravel laravel_roles_permissions_crud
composer create-project laravel/laravel laravel_roles_permissions_crud

Launch the project:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php artisan serve
php artisan serve
php artisan serve

When opening the app url in the browser, you may encounter this error:

could not find driver

This error because laravel default db connection set to sqlite. So to resolve this error open .env and change the DB_CONNECTION to mysql and uncomment the db related env variables like so:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=role_permission_crud
DB_USERNAME=root
DB_PASSWORD=root
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=role_permission_crud DB_USERNAME=root DB_PASSWORD=root
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=role_permission_crud
DB_USERNAME=root
DB_PASSWORD=root

Also in latest laravel versions the session_driver and cache_store is set to database, you may need to use file instead:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
SESSION_DRIVER=file
CACHE_STORE=file
SESSION_DRIVER=file CACHE_STORE=file
SESSION_DRIVER=file
CACHE_STORE=file

Be sure to create the db provided above in phpmyadmin.

 

Preparing and Installing Inertia

To use inertia you have to install the server and client side dependencies. First install inertia laravel by executing this command:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
composer require inertiajs/inertia-laravel
composer require inertiajs/inertia-laravel
composer require inertiajs/inertia-laravel

Once installed publish the inertia middleware:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php artisan inertia:middleware
php artisan inertia:middleware
php artisan inertia:middleware

This command publish the HandleInertiaRequests middleware into the app/Http/Middleware/ directory. This middleware used for data sharing globally and contains version() method for versioning the assets.

Open bootstrap/app.php and append the HandleInertiaRequests middleware:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\App\Http\Middleware\HandleInertiaRequests::class
]);
})
->withMiddleware(function (Middleware $middleware) { $middleware->web(append: [ \App\Http\Middleware\HandleInertiaRequests::class ]); })
->withMiddleware(function (Middleware $middleware) {
        $middleware->web(append: [
           \App\Http\Middleware\HandleInertiaRequests::class
        ]);
    })

Next install the client side dependencies. As we will be using React with Inertia we will need the following dependencies:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm install @inertiajs/react react react-dom @vitejs/plugin-react
npm install @inertiajs/react react react-dom @vitejs/plugin-react
npm install @inertiajs/react react react-dom @vitejs/plugin-react

Also install tailwindcss:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Open the tailwind.config.js and configure the template paths:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./resources/**/*.blade.php",
"./resources/**/*.js",
"./resources/**/*.vue",
"./resources/**/*.jsx"
],
theme: {
extend: {},
},
plugins: [],
}
/** @type {import('tailwindcss').Config} */ export default { content: [ "./resources/**/*.blade.php", "./resources/**/*.js", "./resources/**/*.vue", "./resources/**/*.jsx" ], theme: { extend: {}, }, plugins: [], }
/** @type {import('tailwindcss').Config} */
export default {
  content: [
      "./resources/**/*.blade.php",
      "./resources/**/*.js",
      "./resources/**/*.vue",
      "./resources/**/*.jsx"
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Include tailwindcss directives in resources/css/app.css

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind base; @tailwind components; @tailwind utilities;
@tailwind base;
@tailwind components;
@tailwind utilities;

Next update vite.config.js to include the vite-react plugin:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [
react(),
laravel({
input: ['resources/css/app.css', 'resources/js/app.jsx'],
refresh: true,
}),
],
});
import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [ react(), laravel({ input: ['resources/css/app.css', 'resources/js/app.jsx'], refresh: true, }), ], });
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import react from "@vitejs/plugin-react";

export default defineConfig({
    plugins: [
        react(),
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.jsx'],
            refresh: true,
        }),
    ],
});

 

Booting Inertia App

To boot the inertia app, create new file resources/js/app.jsx and add this code:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import './bootstrap';
import { createInertiaApp } from '@inertiajs/react'
import { createRoot } from 'react-dom/client'
createInertiaApp({
resolve: name => {
const pages = import.meta.glob('./Pages/**/*.jsx', { eager: true })
return pages[`./Pages/${name}.jsx`]
},
setup({ el, App, props }) {
createRoot(el).render(<App {...props} />)
},
})
import './bootstrap'; import { createInertiaApp } from '@inertiajs/react' import { createRoot } from 'react-dom/client' createInertiaApp({ resolve: name => { const pages = import.meta.glob('./Pages/**/*.jsx', { eager: true }) return pages[`./Pages/${name}.jsx`] }, setup({ el, App, props }) { createRoot(el).render(<App {...props} />) }, })
import './bootstrap';

import { createInertiaApp } from '@inertiajs/react'
import { createRoot } from 'react-dom/client'

createInertiaApp({
    resolve: name => {
        const pages = import.meta.glob('./Pages/**/*.jsx', { eager: true })
        return pages[`./Pages/${name}.jsx`]
    },
    setup({ el, App, props }) {
        createRoot(el).render(<App {...props} />)
    },
})

The resolve() callback suppose that we have a Pages/ directory where all pages will be located. The setup() callback mount the app to the dom.

Now compile the assets:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm run dev
npm run dev
npm run dev

 

Root Template

If you opened HandleInertiaRequests middleware you will see this property:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
protected $rootView = 'app';
protected $rootView = 'app';
protected $rootView = 'app';

This tells the root layout that will be used by inertia.Update it like so:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
protected $rootView = 'layouts/frontend';
protected $rootView = 'layouts/frontend';
protected $rootView = 'layouts/frontend';

Then create these two layouts:

  • resources/views/layouts/app.blade.php
  • resources/views/layouts/frontend.blade.php

The app.blade.php will be used for normal blade pages like login, register etc. The frontend.blade.php layout will be used by inertia.

resources/views/app.blade.php

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Roles Permissions CRUD</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,600&display=swap" rel="stylesheet" />
@vite(['resources/css/app.css'])
</head>
<body class="font-sans antialiased dark:bg-black dark:text-white/50">
<div class="bg-gray-50 text-black/50 dark:bg-black dark:text-white/50">
<header class="grid grid-cols-1 gap-2 py-5 px-4">
@if (Route::has('login'))
<nav class="-mx-3 flex flex-1 justify-end">
@auth
<a
href="{{ url('/roles') }}"
class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
>
Roles
</a>
<a
href="{{ url('/permissions') }}"
class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
>
Permissions
</a>
@else
<a
href="{{ route('login') }}"
class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
>
Log in
</a>
@if (Route::has('register'))
<a
href="{{ route('register') }}"
class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
>
Register
</a>
@endif
@endauth
</nav>
@endif
</header>
<main class="mt-6">
@yield('content')
</main>
</div>
</body>
</html>
<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Roles Permissions CRUD</title> <!-- Fonts --> <link rel="preconnect" href="https://fonts.bunny.net"> <link href="https://fonts.bunny.net/css?family=figtree:400,600&display=swap" rel="stylesheet" /> @vite(['resources/css/app.css']) </head> <body class="font-sans antialiased dark:bg-black dark:text-white/50"> <div class="bg-gray-50 text-black/50 dark:bg-black dark:text-white/50"> <header class="grid grid-cols-1 gap-2 py-5 px-4"> @if (Route::has('login')) <nav class="-mx-3 flex flex-1 justify-end"> @auth <a href="{{ url('/roles') }}" class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white" > Roles </a> <a href="{{ url('/permissions') }}" class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white" > Permissions </a> @else <a href="{{ route('login') }}" class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white" > Log in </a> @if (Route::has('register')) <a href="{{ route('register') }}" class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white" > Register </a> @endif @endauth </nav> @endif </header> <main class="mt-6"> @yield('content') </main> </div> </body> </html>
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Roles Permissions CRUD</title>

        <!-- Fonts -->
        <link rel="preconnect" href="https://fonts.bunny.net">
        <link href="https://fonts.bunny.net/css?family=figtree:400,600&display=swap" rel="stylesheet" />
        
        @vite(['resources/css/app.css'])

    </head>
    <body class="font-sans antialiased dark:bg-black dark:text-white/50">
        <div class="bg-gray-50 text-black/50 dark:bg-black dark:text-white/50">
                    <header class="grid grid-cols-1 gap-2 py-5 px-4">

                        @if (Route::has('login'))
                            <nav class="-mx-3 flex flex-1 justify-end">
                                @auth
                                    <a
                                        href="{{ url('/roles') }}"
                                        class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
                                    >
                                        Roles
                                    </a>

                                    <a
                                        href="{{ url('/permissions') }}"
                                        class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
                                    >
                                        Permissions
                                    </a>
                                @else
                                    <a
                                        href="{{ route('login') }}"
                                        class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
                                    >
                                        Log in
                                    </a>

                                    @if (Route::has('register'))
                                        <a
                                            href="{{ route('register') }}"
                                            class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
                                        >
                                            Register
                                        </a>
                                    @endif
                                @endauth
                            </nav>
                        @endif
                    </header>

                    <main class="mt-6">
                        @yield('content')
                    </main>

                </div>
    </body>
</html>

resources/views/frontend.blade.php

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Roles Permissions CRUD</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,600&display=swap" rel="stylesheet" />
@vitereactrefresh
@vite(['resources/css/app.css', 'resources/js/app.jsx'])
@inertiaHead
</head>
<body class="font-sans antialiased dark:bg-black dark:text-white/50">
<div class="bg-gray-50 text-black/50 dark:bg-black dark:text-white/50">
<header class="grid grid-cols-1 gap-2 py-10">
@if (Route::has('login'))
<nav class="-mx-3 flex flex-1 justify-end">
@auth
<a
href="{{ url('/roles') }}"
class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
>
Roles
</a>
<a
href="{{ url('/permissions') }}"
class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
>
Permissions
</a>
@else
<a
href="{{ route('login') }}"
class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
>
Log in
</a>
@if (Route::has('register'))
<a
href="{{ route('register') }}"
class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
>
Register
</a>
@endif
@endauth
</nav>
@endif
</header>
<main class="mt-6">
@inertia
</main>
</div>
</body>
</html>
<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Roles Permissions CRUD</title> <!-- Fonts --> <link rel="preconnect" href="https://fonts.bunny.net"> <link href="https://fonts.bunny.net/css?family=figtree:400,600&display=swap" rel="stylesheet" /> @vitereactrefresh @vite(['resources/css/app.css', 'resources/js/app.jsx']) @inertiaHead </head> <body class="font-sans antialiased dark:bg-black dark:text-white/50"> <div class="bg-gray-50 text-black/50 dark:bg-black dark:text-white/50"> <header class="grid grid-cols-1 gap-2 py-10"> @if (Route::has('login')) <nav class="-mx-3 flex flex-1 justify-end"> @auth <a href="{{ url('/roles') }}" class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white" > Roles </a> <a href="{{ url('/permissions') }}" class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white" > Permissions </a> @else <a href="{{ route('login') }}" class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white" > Log in </a> @if (Route::has('register')) <a href="{{ route('register') }}" class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white" > Register </a> @endif @endauth </nav> @endif </header> <main class="mt-6"> @inertia </main> </div> </body> </html>
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Roles Permissions CRUD</title>

    <!-- Fonts -->
    <link rel="preconnect" href="https://fonts.bunny.net">
    <link href="https://fonts.bunny.net/css?family=figtree:400,600&display=swap" rel="stylesheet" />
    @vitereactrefresh
    @vite(['resources/css/app.css', 'resources/js/app.jsx'])
    @inertiaHead

</head>
<body class="font-sans antialiased dark:bg-black dark:text-white/50">
<div class="bg-gray-50 text-black/50 dark:bg-black dark:text-white/50">
            <header class="grid grid-cols-1 gap-2 py-10">

                @if (Route::has('login'))
                    <nav class="-mx-3 flex flex-1 justify-end">
                        @auth
                            <a
                                href="{{ url('/roles') }}"
                                class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
                            >
                                Roles
                            </a>

                            <a
                                href="{{ url('/permissions') }}"
                                class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
                            >
                                Permissions
                            </a>
                        @else
                            <a
                                href="{{ route('login') }}"
                                class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
                            >
                                Log in
                            </a>

                            @if (Route::has('register'))
                                <a
                                    href="{{ route('register') }}"
                                    class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
                                >
                                    Register
                                </a>
                            @endif
                        @endauth
                    </nav>
                @endif
            </header>

            <main class="mt-6">
                @inertia
            </main>

</div>
</body>
</html>

In the layouts i added links for navigating user to login and register pages in case user not authenticated otherwise we show two links to manage roles and permissions. These modules will be created later.

Also in the layouts you notice that i added the @vite directives that will load the js and css assets. Also for the second layout the inertia related directives @inertiaHead and @inertia directives.

 

Authentication

Before implementing the CRUD operations we have to authenticate the users first. For this we will laravel fortify package to handle authentication.

Install laravel fortify:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
composer require laravel/fortify
composer require laravel/fortify
composer require laravel/fortify

Then publish fortify resources:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php artisan fortify:install
php artisan fortify:install
php artisan fortify:install

After running this command you will see FortifyServiceProvider.php in app/Providers/ directory and app/Actions/Fortify/ directory created.

Migrate the database:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php artisan migrate
php artisan migrate
php artisan migrate

Laravel fortify gives us the routes for authentication such as login but it doesn’t provide the views, for this we need to create the auth views.

So let’s create directory resources/views/auth. I created two views for login and register just for the purpose of this tutorial.

resources/views/auth/register.blade.php

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@extends('layouts.app')
@section('content')
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
<h2 class="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">Create Account</h2>
@if ($errors->any())
<div class="bg-red-400 text-red-800 rounded px-4 py-5">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form method="POST" class="space-y-6" action="{{ route('register') }}">
@csrf
<div>
<label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Name') }}</label>
<input type="text" name="name" value="{{ old('name') }}" required autofocus class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/>
</div>
<div>
<label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Email') }}</label>
<input type="text" name="email" value="{{ old('email') }}" required class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/>
</div>
<div>
<label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Password') }}</label>
<input type="password" name="password" required autocomplete="current-password" class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/>
</div>
<div>
<label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Password Confirm') }}</label>
<input type="password" name="password_confirmation" required class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/>
</div>
<a href="{{ route('login') }}" class="mt-8 text-sm underline">
{{ __('Already have account? Login') }}
</a>
<div>
<button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">
{{ __('Register') }}
</button>
</div>
</form>
</div>
@endsection
@extends('layouts.app') @section('content') <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm"> <h2 class="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">Create Account</h2> @if ($errors->any()) <div class="bg-red-400 text-red-800 rounded px-4 py-5"> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif <form method="POST" class="space-y-6" action="{{ route('register') }}"> @csrf <div> <label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Name') }}</label> <input type="text" name="name" value="{{ old('name') }}" required autofocus class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/> </div> <div> <label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Email') }}</label> <input type="text" name="email" value="{{ old('email') }}" required class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/> </div> <div> <label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Password') }}</label> <input type="password" name="password" required autocomplete="current-password" class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/> </div> <div> <label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Password Confirm') }}</label> <input type="password" name="password_confirmation" required class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/> </div> <a href="{{ route('login') }}" class="mt-8 text-sm underline"> {{ __('Already have account? Login') }} </a> <div> <button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"> {{ __('Register') }} </button> </div> </form> </div> @endsection
@extends('layouts.app')

@section('content')

    <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">

        <h2 class="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">Create Account</h2>

        @if ($errors->any())
            <div class="bg-red-400 text-red-800 rounded px-4 py-5">
                <ul>
                    @foreach ($errors->all() as $error)
                        <li>{{ $error }}</li>
                    @endforeach
                </ul>
            </div>
        @endif

        <form method="POST" class="space-y-6" action="{{ route('register') }}">
            @csrf

            <div>
                <label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Name') }}</label>
                <input type="text" name="name" value="{{ old('name') }}" required autofocus class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/>
            </div>

            <div>
                <label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Email') }}</label>
                <input type="text" name="email" value="{{ old('email') }}" required class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/>
            </div>

            <div>
                <label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Password') }}</label>
                <input type="password" name="password" required autocomplete="current-password" class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/>
            </div>

            <div>
                <label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Password Confirm') }}</label>
                <input type="password" name="password_confirmation" required class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/>
            </div>

                <a href="{{ route('login') }}" class="mt-8 text-sm underline">
                    {{ __('Already have account? Login') }}
                </a>

            <div>
                <button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">
                    {{ __('Register') }}
                </button>
            </div>
        </form>
    </div>
@endsection

resources/views/auth/login.blade.php

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@extends('layouts.app')
@section('content')
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
<h2 class="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">Sign in</h2>
@if ($errors->any())
<div class="bg-red-400 text-red-800 rounded px-4 py-5">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form method="POST" class="space-y-6" action="{{ route('login') }}">
@csrf
<div>
<label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Email') }}</label>
<input type="email" name="email" value="{{ old('email') }}" required autofocus class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/>
</div>
<div>
<label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Password') }}</label>
<input type="password" name="password" required autocomplete="current-password" class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/>
</div>
<div>
<label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Remember me') }}</label>
<input type="checkbox" name="remember" />
</div>
<a href="{{ route('register') }}" class="mt-8 text-sm underline">
{{ __('Create Account') }}
</a>
<div>
<button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">
{{ __('Login') }}
</button>
</div>
</form>
</div>
@endsection
@extends('layouts.app') @section('content') <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm"> <h2 class="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">Sign in</h2> @if ($errors->any()) <div class="bg-red-400 text-red-800 rounded px-4 py-5"> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif <form method="POST" class="space-y-6" action="{{ route('login') }}"> @csrf <div> <label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Email') }}</label> <input type="email" name="email" value="{{ old('email') }}" required autofocus class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/> </div> <div> <label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Password') }}</label> <input type="password" name="password" required autocomplete="current-password" class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/> </div> <div> <label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Remember me') }}</label> <input type="checkbox" name="remember" /> </div> <a href="{{ route('register') }}" class="mt-8 text-sm underline"> {{ __('Create Account') }} </a> <div> <button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"> {{ __('Login') }} </button> </div> </form> </div> @endsection
@extends('layouts.app')

@section('content')

    <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">

        <h2 class="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">Sign in</h2>

        @if ($errors->any())
            <div class="bg-red-400 text-red-800 rounded px-4 py-5">
                <ul>
                    @foreach ($errors->all() as $error)
                        <li>{{ $error }}</li>
                    @endforeach
                </ul>
            </div>
        @endif

    <form method="POST" class="space-y-6" action="{{ route('login') }}">
        @csrf

        <div>
            <label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Email') }}</label>
            <input type="email" name="email" value="{{ old('email') }}" required autofocus class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/>
        </div>

        <div>
            <label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Password') }}</label>
            <input type="password" name="password" required autocomplete="current-password" class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/>
        </div>

        <div>
            <label class="block text-sm font-medium leading-6 text-gray-900">{{ __('Remember me') }}</label>
            <input type="checkbox" name="remember" />
        </div>

        <a href="{{ route('register') }}" class="mt-8 text-sm underline">
            {{ __('Create Account') }}
        </a>

        <div>
            <button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">
                {{ __('Login') }}
            </button>
        </div>
    </form>
    </div>
@endsection

I styled the forms using tailwindcss classes. In the register view i added the form to register user, the form action is set route(‘register’). This router is provided by fortify package.

In the same way the login view contains the login form which have an action provided by fortify which is set route(‘login’).

Let’s tell laravel fortify to use these views for login and register. This can be done in the FortifyServiceProvider::boot() method. add the below code at the beginning of the boot method.

app/Providers/FortifyServiceProvider.php

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
namespace App\Providers;
....
....
....
class FortifyServiceProvider extends ServiceProvider
{
public function boot(): void
{
Fortify::loginView(function () {
return view('auth.login');
});
Fortify::registerView(function() {
return view('auth.register');
});
....
....
....
}
}
<?php namespace App\Providers; .... .... .... class FortifyServiceProvider extends ServiceProvider { public function boot(): void { Fortify::loginView(function () { return view('auth.login'); }); Fortify::registerView(function() { return view('auth.register'); }); .... .... .... } }
<?php

namespace App\Providers;

....
....
....

class FortifyServiceProvider extends ServiceProvider
{
    
    public function boot(): void
    {
        Fortify::loginView(function () {
            return view('auth.login');
        });

        Fortify::registerView(function() {
           return view('auth.register');
        });

        ....
        ....
        ....
    }
}

If you run the application and trying the register and login functionality you will see it work properly. 

You may need to configure the redirect url after successful login or register which can be done in fortify.php config file

config/fortify.php

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
'home' => '/',
'home' => '/',
'home' => '/',

Create the home view in resources/views/home.blade.php

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@extends('layouts.app')
@section('content')
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
@if (session('status'))
<div>
{{ session('status') }}
</div>
@endif
@guest
<h4 class="text-2xl">Hello, <a href="{{route('login')}}">Sign in</a> </h4>
@endguest
@auth
<h4 class="text-2xl">Hello {{auth()->user()->name}}, <a href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('frm-logout').submit();" class="underline text-blue-500">
Logout
</a> </h4>
<form id="frm-logout" action="{{ route('logout') }}" method="POST" style="display: none;">
{{ csrf_field() }}
</form>
@endauth
</div>
@endsection
@extends('layouts.app') @section('content') <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm"> @if (session('status')) <div> {{ session('status') }} </div> @endif @guest <h4 class="text-2xl">Hello, <a href="{{route('login')}}">Sign in</a> </h4> @endguest @auth <h4 class="text-2xl">Hello {{auth()->user()->name}}, <a href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('frm-logout').submit();" class="underline text-blue-500"> Logout </a> </h4> <form id="frm-logout" action="{{ route('logout') }}" method="POST" style="display: none;"> {{ csrf_field() }} </form> @endauth </div> @endsection
@extends('layouts.app')

@section('content')
    <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">

        @if (session('status'))
            <div>
                {{ session('status') }}
            </div>
        @endif

        @guest
            <h4 class="text-2xl">Hello, <a href="{{route('login')}}">Sign in</a> </h4>
        @endguest

        @auth
                <h4 class="text-2xl">Hello {{auth()->user()->name}}, <a href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('frm-logout').submit();" class="underline text-blue-500">
                        Logout
                    </a>   </h4>

                <form id="frm-logout" action="{{ route('logout') }}" method="POST" style="display: none;">
                    {{ csrf_field() }}
                </form>
        @endauth
    </div>
@endsection

Add a route for this view

routes/web.php

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Route::get('/', function () {
return view('home');
});
Route::get('/', function () { return view('home'); });
Route::get('/', function () {
    return view('home');
});

 

Installing Laravel Permissions Package

After making the authentication functionality let’s move to implement the roles and permissions. We will use spatie/laravel-permission package as it provides the necessary Api for dealing with permissions.

Let’s install it first with composer:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
composer require spatie/laravel-permission
composer require spatie/laravel-permission
composer require spatie/laravel-permission

Publish the migration and config file:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"

Now you will see the migration is copied into database/migrations/ directory and config file config/permissions.php

Next clear the cache and run the migrations:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php artisan cache:clear
php artisan config:clear
php artisan cache:clear php artisan config:clear
php artisan cache:clear
php artisan config:clear
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php artisan migrate
php artisan migrate
php artisan migrate

Update the User model to include the HasRoles trait like so:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
namespace App\Models;
....
....
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use HasFactory, Notifiable, HasRoles;
...
...
}
<?php namespace App\Models; .... .... use Spatie\Permission\Traits\HasRoles; class User extends Authenticatable { use HasFactory, Notifiable, HasRoles; ... ... }
<?php

namespace App\Models;

....
....
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasFactory, Notifiable, HasRoles;

    ...
    ...
}

 

Laravel Inertia React Roles Permissions CRUD Part2

5 1 vote
Article Rating

What's your reaction?

Excited
0
Happy
0
Not Sure
0
Confused
0

You may also like

Subscribe
Notify of
guest


0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments