Laravel 11 Database Notifications Example Tutorial

Jun 23, 2024 | Laravel 11 Laravel


Hello Dev,

In this tutorial, I'll demonstrate how to implement database-driven notifications in a Laravel 11 application.

Laravel notifications are a powerful feature allowing you to send messages to users via SMS or email. However, database notifications are often overlooked. They enable us to store notifications in the database and display them to users internally.

Here’s a step-by-step outline of what we'll cover:

1. Setup Authentication: We'll scaffold authentication using Laravel UI.

2. User Roles: Implement two user types: super admin and normal user, distinguished by an "is_admin" column in the users table.

3. Create Posts Table: Set up a posts table with fields for title and body.

4. Post Management: Users can create posts, which require approval by super admins.

5. Notification Setup: Create notifications that are stored in the database when posts are approved.

6. Notification Class: Use an artisan command to generate a notification class and configure the database as the notification driver.

7. Mark Notifications: Implement functionality for users to mark notifications as read.

By following this tutorial, you'll learn how to effectively integrate and utilize Laravel's database notifications for better internal communication within your project.

here's a streamlined version of the steps for sending notifications via the database driver in Laravel 11:

Step 1: Install Laravel 11

Step 2: Scaffold Authentication

Step 3: Create Database Migrations

Step 4: Define and Update Models

Step 5: Generate Notification Classes

Step 6: Define Routes

Step 7: Develop Controllers

Step 8: Create and Update Blade Templates

Step 9: Set Up Admin User

Step 10: Launch Your Laravel Application

Step 1: Install Laravel 10

If you haven't already, create a new Laravel project:

composer create-project laravel/laravel example-app
Step 2: Scaffold Authentication

Use Laravel UI to scaffold authentication views and routes:

composer require laravel/ui
php artisan ui bootstrap --auth
npm install && npm run dev
Read Also: How to Install and Use Laravel 11 Debugbar Package? Step 3: Create Database Migrations

Generate migrations for users, roles, and posts tables:

php artisan make:migration add_is_admin_column_table
php artisan make:migration create_posts_table

Edit the migration files to include the is_admin column in the users table and other necessary fields. For example, in the users table migration, add:

database/migrations/2024_06_18_140624_add_is_admin_column.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->tinyInteger('is_admin')->default(0);
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        //
    }
};
database/migrations/2024_06_18_140906_create_posts_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->string('title');
            $table->text('body');
            $table->boolean('is_approved')->default(false);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

now, Let's run the migration command:

php artisan migrate
Step 4: Define and Update Models

Create models for Post. Define relationships where needed. For instance, in the User model, you might define a one-to-many relationship with Post:

php artisan make:model Post

now, update the model file with hasMany() relationship:

app/Models/Post.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;

    protected $fillable = ['title', 'body', 'is_approved', 'user_id'];

    /**
     * Write code on Method
     *
     * @return response()
     */
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}
app/Models/User.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'email',
        'password',
        'is_admin'
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * Get the attributes that should be cast.
     *
     * @return array
     */
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }
}
Step 5: Create Notification

Generate a notification class:

php artisan make:notifications-table
php artisan migrate

next, run the following command to create notification class.

php artisan make:notification PostApprovedNotification

Now you can see a new folder created as "Notifications" in the app folder. You need to make the following changes as shown in the class below.

app/Notifications/PostApprovedNotification.php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\DatabaseMessage;
use App\Models\Post;

class PostApprovedNotification extends Notification
{
    use Queueable;

    protected $post;

    /**
     * Create a new notification instance.
     *
     * @param Post $post
     */
    public function __construct(Post $post)
    {
        $this->post = $post;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param mixed $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['database'];
    }

    /**
     * Get the array representation of the notification.
     *
     * @param mixed $notifiable
     * @return array
     */
    public function toDatabase($notifiable)
    {
        return [
            'post_id' => $this->post->id,
            'title' => $this->post->title,
            'body' => $this->post->body,
            'message' => "Your post titled '{$this->post->title}' has been approved."
        ];
    }
}
Step 6: Create Routes

Define routes for creating posts, approving posts, and viewing notifications in routes/web.php:

routes/web.php
<?php

use Illuminate\Support\Facades\Route;

use App\Http\Controllers\PostController;

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');

Route::middleware('auth')->group(function () {
    Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
    Route::post('/posts', [PostController::class, 'store'])->name('posts.store');
    Route::get('/posts/{id}/approve', [PostController::class, 'approve'])->name('posts.approve');
    Route::get('/notifications/{id}/mark-as-read', [PostController::class, 'markAsRead'])->name('notifications.mark.as.read');
});
Step 7: Create Controller

Generate controllers for Post and Notification:

app/Http/Controllers/PostController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Post;
use App\Notifications\PostApprovedNotification;

class PostController extends Controller
{
    /**
     * Write code on Method
     *
     * @return response()
     */
    public function index(Request $request)
    {
        $posts = Post::get();

        return view('posts', compact('posts'));
    }

    /**
     * Write code on Method
     *
     * @return response()
     */
    public function store(Request $request)
    {
        $this->validate($request, [
             'title' => 'required',
             'body' => 'required'
        ]);
   
        $post = Post::create([
            'user_id' => auth()->id(),
            'title' => $request->title,
            'body' => $request->body
        ]);
   
        return back()->with('success','Post created successfully.');
    }

    /**
     * Write code on Method
     *
     * @return response()
     */
    public function approve(Request $request, $id)
    {
        if (!auth()->user()->is_admin) {
            return back()->with('success', 'you are not super admin.');
        }

        $post = Post::find($id);

        if ($post & !$post->is_approved) {
            $post->is_approved = true;
            $post->save();

            // Notify the user
            $post->user->notify(new PostApprovedNotification($post));

            return back()->with('success','Post approved and user notified.');
        }

        return back()->with('success', 'Post not found or already approved.');
    }

    /**
     * Write code on Method
     *
     * @return response()
     */
    public function markAsRead(Request $request, $id)
    {
        $notification = auth()->user()->unreadNotifications->find($id);
        $notification->markAsRead();

        return back()->with('success', 'Added Mark as read.');
    }
}
Read Also: How to Create Like Dislike Button in Laravel 11? Step 8: Create and Update Blade Files

Create views for listing posts, approving posts, and displaying notifications. Use Blade syntax to render dynamic content based on user roles and notification status.

resources/views/layouts/app.blade.php
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.bunny.net">
    <link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">

    <!-- Scripts -->
    @vite(['resources/sass/app.scss', 'resources/js/app.js'])

    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />

    <style type="text/css">
        .close{
            position: absolute;
            top: 0;
            right: 0;
            z-index: 2;
            padding: 1.25rem 1rem;
            text-decoration: none;
        }
    </style>
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
            <div class="container">
                <a class="navbar-brand" href="{{ url('/') }}">
                    Laravel Send Notification Via Database as Driver
                </a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
                    <span class="navbar-toggler-icon"></span>
                </button>

                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <!-- Left Side Of Navbar -->
                    <ul class="navbar-nav me-auto">

                    </ul>

                    <!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ms-auto">
                        <!-- Authentication Links -->
                        @guest
                            @if (Route::has('login'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
                                </li>
                            @endif

                            @if (Route::has('register'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
                                </li>
                            @endif
                        @else
                            <li class="nav-item">
                                <a class="nav-link" href="{{ route('posts.index') }}">{{ __('Posts') }}</a>
                            </li>
                            <li class="nav-item dropdown">
                                <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                    {{ Auth::user()->name }}
                                </a>

                                <div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
                                    <a class="dropdown-item" href="{{ route('logout') }}"
                                       onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                                        {{ __('Logout') }}
                                    </a>

                                    <form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
                                        @csrf
                                    </form>
                                </div>
                            </li>
                        @endguest
                    </ul>
                </div>
            </div>
        </nav>

        <main class="py-4">
            @yield('content')
        </main>
    </div>
</body>
</html>
resources/views/posts.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-12">
            <div class="card">
                <div class="card-header"><i class="fa fa-list"></i> {{ __('Posts List') }}</div>

                <div class="card-body">
                    @session('success')
                        <div class="alert alert-success" role="alert"> 
                            {{ $value }}
                        </div>
                    @endsession

                    
                    @foreach(auth()->user()->unreadNotifications as $notification)
                        <div class="alert alert-success alert-dismissible fade show">
                            <span><i class="fa fa-circle-check"></i>  [{{ $notification->created_at }}] {{ $notification->data['message'] }}</span>
                            <a href="{{ route('notifications.mark.as.read', $notification->id) }}" class="close" data-dismiss="alert" aria-label="Close">
                                <span aria-hidden="true"><strong><i class="fa fa-book-open"></i> Mark as Read</strong></span>
                            </a>
                        </div>
                    @endforeach
                    
                    @if(!auth()->user()->is_admin)
                    <p><strong>Create New Post</strong></p>
                    <form method="post" action="{{ route('posts.store') }}" enctype="multipart/form-data">
                        @csrf
                        <div class="form-group">
                            <label>Title:</label>
                            <input type="text" name="title" class="form-control" />
                            @error('title')
                                <div class="text-danger">{{ $message }}</div>
                            @enderror
                        </div>
                        <div class="form-group">
                            <label>Body:</label>
                            <textarea class="form-control" name="body"></textarea>
                            @error('body')
                                <div class="text-danger">{{ $message }}</div>
                            @enderror
                        </div>
                        <div class="form-group mt-2">
                            <button type="submit" class="btn btn-success btn-block"><i class="fa fa-save"></i> Submit</button>
                        </div>
                    </form>
                    @endif

                    <p class="mt-4"><strong>Post List:</strong></p>
                    <table class="table table-bordered data-table">
                        <thead>
                            <tr>
                                <th width="70px">ID</th>
                                <th>Title</th>
                                <th>Body</th>
                                <th>Status</th>
                                @if(auth()->user()->is_admin)
                                <th>Action</th>
                                @endif
                            </tr>
                        </thead>
                        <tbody>
                            @forelse($posts as $post)
                                <tr>
                                    <td>{{ $post->id }}</td>
                                    <td>{{ $post->title }}</td>
                                    <td>{{ $post->body }}</td>
                                    <td>
                                        @if($post->is_approved)
                                            <span class="badge bg-success"><i class="fa fa-check"></i> Approved</span>
                                        @else
                                            <span class="badge bg-primary"><i class="fa fa-circle-dot"></i> Pending</span>
                                        @endif
                                    </td>
                                    @if(auth()->user()->is_admin)
                                    <td>
                                        @if(!$post->is_approved)
                                            <a href="{{ route('posts.approve', $post->id) }}" class="btn btn-success btn-sm"><i class="fa fa-save"></i> Approved</a>
                                        @endif
                                    </td>
                                    @endif
                                </tr>
                            @empty
                                <tr>
                                    <td colspan="5">There are no posts.</td>
                                </tr>
                            @endforelse
                        </tbody>
                    </table>

                </div>
            </div>
        </div>
    </div>
</div>
@endsection
Step 9: Create Admin User

Create an admin user for testing purposes:

php artisan make:seeder CreateAdminUser
database/seeders/CreateAdminUser.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\User;

class CreateAdminUser extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        User::create([
            'name' => 'Admin',
            'email' => 'admin@gmail.com',
            'password' => bcrypt('123456'),
            'is_admin' => 1
        ]);
    }
}

now, the run seeder using the following command:

php artisan db:seed --class=CreateAdminUser
Step: Run the Application

Now, to run the Laravel application, please type the following command and press enter:

php artisan serve

Now, open your web browser and enter the provided URL to view the output of the application.

View Your Application
http://localhost:8000/

Thank you for your encouragement! If you have any questions or need further assistance, feel free to ask. I'm here to help!



Tags :
#Laravel 11
#Laravel
ItErrorSolution.com

ItErrorSolution.com

"Hey there! I'm a full-stack developer and proud owner of ItErrorSolution.com, based right here in India. I love nothing more than sharing handy tips and tricks with my fellow developers through easy-to-follow tutorials. When it comes to coding, I'm all about PHP, Laravel, Angular, Vue, Node, JavaScript, jQuery, CodeIgniter, and Bootstrap – been hooked on them forever! I'm all about putting in the work and staying committed. Ready to join me on this journey to coding?"