Posted on 51 Comments

Web Push Using Laravel 8 Notifications *Updated*

Web Push Is Awesome

No seriously! It’s a pivotal moment in web development. You see web push is a W3C protocol that allows websites to communicate with a user’s browser in the background, using this web developers can now do things such as: background push notifications, offline sync, and background analytics just to name few. The web push api follows the protocol and consist of 4 main stages:

  1. User Agent installs service worker
  2. App asks permission to show notifications
  3. Once permission is granted, subscribe to push
  4. Save subscription details on backend
  5. Send notification via Laravel notification

All the code can be seen on my Github and the live demo can be seen here!

Components Of Web Push

Implementing web push requires a true full-stack approach, on the back-end we have to:

  • Implement the WebPush notification channel
  • Set up VAPID keys (more details later)
  • Configure the user model to manage subscriptions
  • Fire the notifications

On the front end we must:

  • Create and install a service worker, the majority of our logic will be contained here and is responsible to handling push notifications
  • Give user prompt to accept permissions
  • Give user ability to send notification to self

I will break it down into back-end and front-end implementations as to not confuse you; let’s start with the back-end.
 

Creating The Backend

Create a blank Laravel application and run the following composer command to download the web push notification channel.

composer require laravel-notification-channels/webpush

The User Model

Next open up your user model and add the following.

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use NotificationChannels\WebPush\HasPushSubscriptions;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable, HasPushSubscriptions;

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

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

/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}

This added HasPushSubscriptions trait allows the user model to receive push notifications. Without this trait the application won’t know what model to store the tokens on.


Next publish the migration with:

php artisan vendor:publish --provider="NotificationChannels\WebPush\WebPushServiceProvider" --tag="migrations"

Run the migrate command to create the necessary tables:

php artisan migrate

You can also publish the config file with the following command. We won’t be doing any customizations in part 1 but maybe down the line:

php artisan vendor:publish --provider="NotificationChannels\WebPush\WebPushServiceProvider" --tag="config"

Generate the VAPID keys with (required for browser authentication) with the following artisan command. This command will set VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEYin your .env file. VAPID is a web push protocol that is needed if we want to send push notifications. Basically it voluntarily identifies itself to a push notification server. If you want to read the specification you can here:

php artisan webpush:vapid

The Notification Class

Next let’s create the notification being used. For now we will make a generic catch all notification. In later tutorials we may add some more custom notifications:

php artisan make:notification GenericNotification

Open up the file and replace with the following contents

<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use NotificationChannels\WebPush\WebPushMessage;
use NotificationChannels\WebPush\WebPushChannel;
class GenericNotification extends Notification
{
    use Queueable;
    public $title, $body;
    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct($title, $body)
    {
        //
        $this->title = $title;
        $this->body = $body;
    }
    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return [WebPushChannel::class];
    }
    public function toWebPush($notifiable, $notification)
    {
      $time = \Carbon\Carbon::now();
        return WebPushMessage::create()
            // ->id($notification->id)
            ->title($this->title)
            ->icon(url('/push.png'))
            ->body($this->body);
            //->action('View account', 'view_account');
    }
}

Let’s break down this class. First off in the constructor() method we set the title and the body of the notification. This will be used to show the title and body of the push notification on the browser. The via() method we want to set the channel to the WebPushChannel. This should be self explanatory as we want to deliver via push notifications.

The API Routes

Next open up routes/api.php and fill out the API routes

<?php
use Illuminate\Http\Request;
use App\Models\User; /* |-------------------------------------------------------------------------- | API Routes |-------------------------------------------------------------------------- | | Here is where you can register API routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | is assigned the "api" middleware group. Enjoy building your API! | */ Route::middleware('auth:api')->get('/user', function (Request $request) { return $request->user(); }); Route::post('/save-subscription/{id}',function($id, Request $request){ $user = \App\Model\User::findOrFail($id); $user->updatePushSubscription($request->input('endpoint'), $request->input('keys.p256dh'), $request->input('keys.auth')); $user->notify(new \App\Notifications\GenericNotification("Welcome To WebPush", "You will now get all of our push notifications")); return response()->json([ 'success' => true ]); }); Route::post('/send-notification/{id}', function($id, Request $request){ $user = \App\Model\User::findOrFail($id); $user->notify(new \App\Notifications\GenericNotification($request->title, $request->body)); return response()->json([ 'success' => true ]); });

As you can see, we only added two additional routes to the application, both of which are POST. The first route

/save-subscription/{id}

Is responsible for saving the web push subscription from the client. Once the client requests web push access a set of keys are generated, which must be sent to our server. Using the updatePushSubscription() method that comes with the trait added to the user model, we can set the webpush subscription for whatever url the user is requesting from. The updatePushSubscription method takes 3 parameters:

  • endpoint (required): This is the created on the front end when registering for push notifications
  • key (optional) : Needed to encrypt data, only encrypted messages can have a payload
  • token (optional): Needed to encrypt data, only encrypted messages can have a payload

That’s it for the back end! In the next tutorial we will make a Vue.js application and register for push notifications and test it out! If you enjoyed my content please like/subscribe/share! If you want another cool project learn how to stream your desktop to a recording or to YouTube!

51 thoughts on “Web Push Using Laravel 8 Notifications *Updated*

  1. Can u pls subscribe the solution a bit more?

    1. Absolutely, in which aspect? I can edit the post and be more in depth.

    2. Check it now

  2. i want to notification send like facebook. when i commented on another user photo and that user get notification
    using popup("abc user is commented on your photos.")

    1. I will write it up this weekend.

  3. if possible then give small demo of that application plz.
    thank u in advance.

    1. Did you check webpush.jyroneparker.com? I can add a video demo I will do it tomorrow.

  4. ???

  5. hey im getting error like this
    Symfony\Component\Debug\Exception\FatalThrowableError: Call to undefined method NotificationChannels\WebPush\WebPushMessage::create() in file /var/www/html/push/app/Notifications/GenericNotification.php on line 42
    can you please solve this

    1. Did you run composer install?

      1. yeah and im getting bad status code from server
        Failed to register/update a ServiceWorker for scope ‘http://127.0.0.1:8000/’: Load failed with status 404 for script ‘http://127.0.0.1:8000/sw.js’

        1. You can't do 127.0.0.1 unless you change the setting in chrome or whatever browser. Has to be https.

          1. uhh. thanks MASTASHAKE for your reply… could you please give me a your fb or any chat details that belongs to you. then i can get in touch with you always if i face any problems. cause in this comments i can’t make it good conversation about that problems iam facing.. i have many doubts to ask you. Hope you will understand.

          2. https://www.facebook.com/jyrone.parker.1

  6. Hi,
    I’m getting bad response from server. Can you tell me what’s wrong?
    Thanks

    1. I'll check and get back

  7. I have this error

    1. I have this error
      Your requirements could not be resolved to an installable set of packages.
      Problem 1
      – pusher/pusher-php-server v3.0.1 requires ext-curl * -> the requested PHP extension curl is missing from your system.
      – pusher/pusher-php-server 3.0.0 requires ext-curl * -> the requested PHP extension curl is missing from your system.
      – Installation request for pusher/pusher-php-server ^3.0 -> satisfiable by pusher/pusher-php-server[3.0.0, v3.0.1].

      1. You must enable the php cURL extension on your machine

  8. I am using uuid for my user id's as well as a custom user table and this is causing some odd issues.
    What areas do i need to change to get this working again as i see several references back and forth.

    1. Can you be specific

  9. Hi Jyrone, whats up ?
    Firstly, thankyou for this tutorial. This help me a lot. But, i think have one error in the begin.
    The class that you inform to put into providers array are wrong, because are like this: NotificationChannelsWebPushWebPushServiceProvider::class,
    Maybe, the correct is: NotificationChannels\WebPush\WebPushServiceProvider::class,
    Thanks again!

    1. Oh you are right! I will edit

  10. Hello Jyrone Parker,
    I am not getting push notification on mobile browsers. Can you please provide the solution.
    Thank You.

    1. Is it working on browser?

  11. Thanks for this tutorials , I have project which I want to send notifications to guest not to register user, so I will add table contain IP and browser agent and connect this modal with subscriber table , but I have one unclear idea how can I remove endpoint from subscriber table when user chose block after they allow it with in browser you can see the image to be more clear http://nimb.ws/A7NPyR
    Thanks in advance

    1. Webworkers have an event that can handle the uninstall event. You would have to do an http post after that event is called. I'm sure there is another way but I can't think of any off top.

  12. Hello I am getting “Failed to fetch” error after coming back from backend that is api. In api the notify line is showing some error. Can not able to fix it.

    1. Paste the whole error

      1. POST http://localhost/webpush/public/api/save-subscription/1 0 ()
        error fetching subscribe TypeError: Failed to fetch.
        If i remove the code:
        $user->notify(new App\Notifications\GenericNotification("Welcome To WebPush", "You will now get all of our push notifications"));
        Then there will be no error

      2. Can I know the functionality of notify there?

        1. Add me on skype I'll help you parker,jyrone

          1. hi Jyrone, im having a similar issue.... when I enable push notifications, the web console is displaying this:
            POST https://bitcoinmerida.com/api/save-subscription/1 500 (Internal Server Error)
            I did not touch the code, and database is working fine. hmmm must be something simpler, I will continue checking, but so far nothing... can you help?

          2. Hi Jyrone, when I click Enable Notifications, my web console is displaying the endpoint, the keys, and then after a 500 Internal Server Error, which dissappears if I comment out
            $user->notify(new \App\Notifications\GenericNotification(); also posted on youtube 😀

        2. I have similar error, very much alike, how did you solved it?

        3. I have the same error, on $user->notify, how did you solved the issue?

  13. Push event in service worker is never triggered in my case. Can you please explain me how and push event in service worker gets triggered?

    1. Are you on localhost?

      1. the same problem I am getting. all things are working fine but notification is not coming.. I am trying the same by localhost

        1. You have to use a domain name localhost will not work

  14. I want to try this out but is it working on laravel 5.7?

  15. I'm getting an error. When I enter the "php artisan webpush:vapid", the result I get is "RuntimeException Unable to create the key". How can I resolve this problem?

    1. Did you install the dependencies with composer?

      1. Yes, I did. The same problem here.

  16. Hi Jyrone,

    I am getting success message in response, even allow notifications in browser, but notifications are not coming.

    Response in console:-
    Service worker successfully registered.
    {"success":true}
    Received PushSubscription: {"endpoint":"xxxxxx","expirationTime":xxxxxx,"keys":{"p256dh":"xxxxxx","auth":"xxxxxx"}}

    1. What’s your JavaScript look like

      1. Same as above

        1. What domain are you sending from? Is it localhost or you have an actual domain?

          1. I am using actual domain, now its working in firefox but not working in chrome browser.

          2. Can you console.log your output

          3. Send me the domain

Leave a Reply