Github repo
https://github.com/emuroiwa/guestbook-laravel-acl-crud
Lets start
Almost tempted to do a Vue SPA, but no K.I.S.S. This is CRUD laravel guestbook app, with 2 user roles 1) Admin and 2) the user (guest).
I will be using spaite package this package allows you to manage user permissions and roles in a database.
Lets Code
- Install Laravel
We are going to install a fresh new laravel project
composer create-project --prefer-dist laravel/laravel guestbook
2. Laravel Auth
Make auth in your laravel project.
composer require laravel/ui
php artisan ui bootstrap --auth
npm install
npm run dev
3. Install spaite package
composer require spatie/laravel-permission
once done add the service provider and aliase in config/app.php
'providers' => [
....
Spatie\Permission\PermissionServiceProvider::class,
],

We need to take care of our permissions
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
Run the migrations, after setting up you DB details in the .env file
php artisan migrate
3. Models and Migrations
Create a message model and migration
php artisan make:model Message -m
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateMessagesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('messages', function (Blueprint $table) {
$table->id();
$table->string('message_title');
$table->longText('message_body');
$table->unsignedBigInteger('user_id')->unsigned();
$table->timestamps();
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('messages');
}
}
once again run the migration 🙂
php artisan migrate
Message Model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Message extends Model
{
//
protected $fillable = [
'message_title', 'message_body', 'user_id'
];
}
Edit the User Model

Middleware and Spaite

Controllers
php artisan make:controller RoleController --resource
php artisan make:controller UserController --resource
php artisan make:controller MessageController --resource
php artisan make:controller MessageReplyController --resource
MessageController
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Message;
class MessageController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
function __construct()
{
$this->middleware('permission:message-list|message-create|message-edit|message-delete', ['only' => ['index','show']]);
$this->middleware('permission:message-create', ['only' => ['create','store']]);
$this->middleware('permission:message-edit', ['only' => ['edit','update']]);
$this->middleware('permission:message-delete', ['only' => ['destroy']]);
}
public function index()
{
//
$messages = Message::orderBy('id','DESC')->get();
return view('messages.index',compact('messages'))
->with($messages);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('messages.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//validate
request()->validate([
'message_title' => 'required',
'message_body' => 'required',
'user_id' => 'required',
]);
Message::create($request->all());
return redirect()->route('messages.index')
->with('success','Message sent successfully.');
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
return view('messages.show',compact('message'));
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
return view('messages.edit',compact('message'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
request()->validate([
'message_title' => 'required',
'message_body' => 'required',
'user_id' => 'required',
]);
$message->update($request->all());
return redirect()->route('messages.index')
->with('success','Message updated successfully');
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
$message->delete();
return redirect()->route('messages.index')
->with('success','Message deleted successfully');
}
}
Message Reply Controller
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\MessageReply;
use Auth;
class MessageReplyController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
function __construct()
{
$this->middleware('permission:reply-list|reply-create|reply-edit|reply-delete',
['only' => ['index','show']]);
$this->middleware('permission:reply-create', ['only' => ['create','store']]);
$this->middleware('permission:reply-edit', ['only' => ['edit','update']]);
$this->middleware('permission:reply-delete', ['only' => ['destroy']]);
}
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//validate
request()->validate([
'message_body' => 'required',
]);
// get logged user-.id
$user = Auth::user();
MessageReply::create([
'reply_body' => $request['message_body'],
'user_id' => $user->id,
'message_id' => $request['message_id']
]);
return redirect()->back()
->with('success','Reply sent successfully.');
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
// handled in messags controller
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
request()->validate([
'reply_body' => 'required',
]);
$reply = MessageReply::findOrFail($id);
$reply->update($request->all());
return redirect()->back()
->with('success','Reply updated successfully');
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
$reply = MessageReply::findOrFail($id);
$reply->delete();
return redirect()->back()
->with('success','Reply deleted successfully');
}
}
Laravel Seeder
Create Seeder For Permissions and AdminUser
php artisan make:seeder PermissionTableSeeder
php artisan make:seeder UserPermissionSeeder
php artisan make:seeder CreateAdminSeeder
php artisan make:seeder CreateUserSeeder
<?php
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission;
class PermissionTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$permissions = [
'role-list',
'role-create',
'role-edit',
'role-delete',
'message-list',
'message-create',
'message-edit',
'message-delete'
];
foreach ($permissions as $permission) {
Permission::create(['name' => $permission]);
}
}
}
CreateAdminSeeder
<?php
use Illuminate\Database\Seeder;
use App\User;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
class CreateAdminSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$user = User::create([
'name' => 'Payfast Admin',
'email' => 'admin@payfast.co.za',
'password' => bcrypt('payfast')
]);
$role = Role::create(['name' => 'Admin']);
$permissions = Permission::pluck('id','id')->all();
$role->syncPermissions($permissions);
$user->assignRole([$role->id]);
}
}
User Seeder
<?php
use Illuminate\Database\Seeder;
use App\User;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
class CreateUserSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$user = User::create([
'name' => 'Payfast User',
'email' => 'user@payfast.co.za',
'password' => bcrypt('payfast')
]);
$role = Role::create(['name' => 'User']);
$permissions = Permission::pluck('id','id')
->where('name', 'LIKE', '%message%')
->orWhere('name','=',"reply-list")
->get();
$role->syncPermissions($permissions);
$user->assignRole([$role->id]);
}
}
User Permission Seeder
<?php
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role;
class UserPermissionSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$role = Role::firstOrCreate(['name' => 'User']);
$role->syncPermissions([
'message-list',
'message-create',
'message-edit',
'message-delete',
'reply-list',
]);
}
}
Add Seeders to Database Seeder
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
$this->call(PermissionTableSeeder::class);
$this->call(UserPermissionSeeder::class);
$this->call(CreateAdminSeeder::class);
$this->call(CreateUserSeeder::class);
}
}
Run Seeder
php artisan db:seed --class=DatabaseSeeder
Blade templates
Messages Blades
In your resources/views folder create a messages folder, then create these blade templates
- index.blade.php
- edit.blade.php
- create.blade.php
- show.blade.php
index.blade.php
@extends('layouts.app')
@section('content')
<div class="row">
<div class="col-lg-12 margin-tb">
<div class="pull-left">
<h2>Inbox</h2>
</div>
</div>
</div>
@if ($message = Session::get('success'))
<div class="alert alert-success">
<p>{{ $message }}</p>
</div>
@endif
<div class="card">
<div class="card-header">
@can('message-create')
<a class="btn btn-success" href="{{ route('inbox.create') }}"> Create New message</a>
@endcan
</div>
<div class="card-body">
<table class="table table-striped">
<tr>
<th>Name</th>
<th>Title</th>
<th width="280px">Action</th>
</tr>
@foreach ($messages as $message)
<tr>
<td>{{ $message->name }}</td>
<td>{{ $message->message_title }}</td>
<td>
<form action="{{ route('inbox.destroy',$message->id) }}" method="POST">
<a class="btn btn-info" href="{{ route('inbox.show',$message->id) }}">Show</a>
@can('message-edit')
<a class="btn btn-primary" href="{{ route('inbox.edit',$message->id) }}">Edit</a>
@endcan
@csrf
@method('DELETE')
@can('message-delete')
<button type="submit" class="btn btn-danger">Delete</button>
@endcan
</form>
</td>
</tr>
@endforeach
</table>
{{ $messages->links() }}
</div>
</div>
@endsection
edit.blade.php
@extends('layouts.app')
@section('content')
<div class="row">
<div class="col-lg-12 margin-tb">
<div class="pull-left">
<h2>Edit Message</h2>
</div>
</div>
</div>
@if ($errors->any())
<div class="alert alert-danger">
<strong>Error !</strong> Check your input.<br><br>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card">
<div class="card-header">
<a class="btn btn-primary" href="{{ route('inbox.index') }}"> Back</a>
</div>
<div class="card-body">
<form action="{{ route('inbox.update',$message->id) }}" method="POST">
@csrf
@method('PUT')
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Title:</strong>
<input type="text" name="message_title" value="{{ $message->message_title }}" class="form-control" >
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Message:</strong>
<textarea class="form-control" style="height:150px" name="message_body" placeholder="Detail">{{ $message->message_body }}</textarea>
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12">
<button type="submit" class="btn btn-primary">Update</button>
</div>
</div>
</form>
</div>
</div>
@endsection
create.blade.php
<!-- extend layout -->
@extends('layouts.app')
<!-- content -->
@section('content')
<div class="row">
<div class="col-lg-12 margin-tb">
<div class="pull-left">
<h2>Create New Message</h2>
</div>
</div>
</div>
@if ($errors->any())
<div class="alert alert-danger">
<strong>Error!</strong> Check your input.<br><br>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('inbox.store') }}" method="POST">
@csrf
<div class="card">
<div class="card-header">
<a class="btn btn-primary" href="{{ route('inbox.index') }}"> Back</a>
</div>
<div class="card-body">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Title:</strong>
<input type="text" name="message_title" class="form-control" placeholder="Title">
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Message:</strong>
<textarea class="form-control" style="height:150px" name="message_body" placeholder="Message"></textarea>
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12">
<button type="submit" class="btn btn-primary">Send</button>
</div>
</div>
</div>
</div>
</form>
@endsection
show.blade.php
@extends('layouts.app')
@section('content')
<div class="row">
<div class="col-lg-12 margin-tb">
<div class="pull-left">
<h2> Message</h2>
</div>
</div>
</div>
@if ($message = Session::get('success'))
<div class="alert alert-success">
<p>{{ $message }}</p>
</div>
@endif
@if(isset($messages))
<div class="card">
@can('reply-create')
<div class="card-header">
<a class="btn btn-primary" data-toggle="modal" data-target="#myModal"> Reply</a>
</div>
@endcan
<div class="card-body">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Title:</strong>
<p>{{ $messages->message_title }}</p>
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Body:</strong>
<p>{{ $messages->message_body }}</p>
</div>
<small>{{ $messages->created_at }}</small>
</div>
</div>
</div>
</div>
@endif
@if(isset($replies))
@foreach($replies as $reply)
<div class="card mt-2">
<div class="card-body">
<label class="badge badge-info">{{ $reply->name }}</label>
<p> {{ $reply->reply_body }} </p>
<small>{{ $reply->created_at }}</small>
</div>
<div class="card-footer">
<form action="{{ route('reply.destroy',$reply->id) }}" method="POST">
@csrf
@method('DELETE')
@can('reply-delete')
<button type="submit" class="btn btn-danger">Delete</button>
@endcan
</form>
</div>
</div>
@endforeach
@endif
<!-- Modal -->
<div id="myModal" class="modal fade" role="dialog">
<div class="modal-dialog modal-lg">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Reply</h4>
<button type="button" class="close" data-dismiss="modal">×</button>
</div>
<form action="{{ route('reply.store') }}" method="POST">
@csrf
<div class="modal-body">
<div class="form-group">
<strong>Message:</strong>
<textarea class="form-control" style="height:150px" name="message_body" placeholder="Message" required></textarea>
{{ Form::hidden('message_id', $messages->id, array('id' => 'message_id')) }}
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Send</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</form>
</div>
</div>
</div>
@endsection
Lets test it out for a minute
php artisan serve