Notifications let apps reach users with short messages. They’re increasingly interactive and used to drive engagement, hence a must for developers.

Welcome to the great world of notifications! Using…huh?… Laravel? Aren’t notifications a mobile phone thing?

Well… kind of. Notifications are now broadly used not just on mobile but also on desktop browsers (yep I’m talking about the annoying “do you want to allow” popup on your favorite browser).

Revisiting the Contact form “Submitted Success” message

For the sake of showing a better alternative than popping up a modal or showing a notification on some part of a web page, we are going to build a simple Contact Form where a successful submit will be shown as a push notification.

While there are already a bunch of front and backend combinations that can deal with this kind of functionality, for the sake of greatness, we are going to use Laravel as our backend and Vue.js as our frontend. Laravel will deal with the message broadcasting, while Vue.js will mainly deal with our application view.

For our notification service, we will be using Pusher. Of course, we could mainly just use Laravel and Vue.js, but Pusher is pretty fresh and offers some great features that make it easy scaling our notification system to a broader use.

Enough with the talk; let’s work!

Setting up Pusher

First, we need to create a Pusher account while setting up our application:

Install and config our Laravel Application

If you don’t have Laravel installed, shame on you! To mend this quickly, you can always go here and grab it, install it, and start to develop beautiful things.

As usual, on Laravel, you can create a brand new application using the following command on the terminal:

laravel new contact-form-notifications

Afterward, we will need to install the Pusher PHP SDK. We can do this with the help of our dear friend composer, with the following command:

composer require pusher/pusher-php-server

Additionally, we will need to install our Laravel Frontend Dependencies, which include things such as Bootstrap, Axious, Vue.js and other nice to have:

npm install

For the broadcast event to be alive, we will need to install Laravel Echo as well as Pusher-js:

npm install -S laravel-echo pusher-js

Now we need to configure Laravel to use Pusher as its broadcast driver. For this to happen, we need to edit our .env file on our Laravel project’s directory root, with the config variables provided by Pusher (can be found at your Channel App > App Keys).

So, in our .env file we should have something like this:

PUSHER_APP_ID=322700
BROADCAST_DRIVER=pusher

// Get the credentials from your pusher dashboard
PUSHER_APP_ID=XXXXX
PUSHER_APP_KEY=XXXXXXX
PUSHER_APP_SECRET=XXXXXXX

Now we need to change our ./config/broadcasting.php file generated by Laravel, to use Pusher as our default broadcaster, by changing the following line:

'default' => env('BROADCAST_DRIVER', 'pusher'),

And for our final configuration, let us bring joy between Echo and Pusher by uncommenting and editing the values at the bottom of resources/js/bootstrap.js

import Echo from 'laravel-echo'

window.Pusher = require('pusher-js');

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: process.env.MIX_PUSHER_APP_KEY,
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    encrypted: true
});

 

Model and DB configuration

Well, first of all, we will need a table to store all sent contact form data. For this, we will use a Laravel Migration file, and an Eloquent model.

We will call our model: Message, and we can create it by running:

php artisan make:model Message -m -c

The -m and -c stand for:

  • Automatically generate the migration file;
  • Automatically generate the controller file;

The generated migration file will need some changes though, so go to your ./database/migrations folder and change the up function to look like this:

public function up()
    {
        Schema::create('messages', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('email');
            $table->text('description');
            $table->timestamps();
        });
    }

Warning: You may need to configure the default string length. See this.

Afterward, you should update your database details (if you didn’t already) on your .env file.

Now we can safely run:

php artisan migrate

Oh, wait! We should also set our model’s attributes as mass assignable. So the Message Model should look something like this:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Message extends Model
{
    /**
     * Mass assignable attributes
     *
     * @var array
     */
    protected $fillable = ['name', 'email', 'description'];
}

Persisting our Message

Ok! Now that we already have our DB and our Model set up, we really need to persist our data. For this, we will need a route and a controller function to save a new Message.

The route will be part of the API’s routes list (in ./routes/api.php, and will be callable by the front-end in order to save our messages. Keep in mind that any route here defined will belong to the API middleware group.

The route will look like:

Route::post('/message', 'MessageController@store');

And the controller will be something like:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class MessageController extends Controller
{
  /**
   * Saves a new Message into the DB
   * @var Request
   */
  public function store(Request $request) {
    // get data to be saved
    $data = $request->only(['name', 'email', 'description']);

    // save message and assign return value of created message to $message array
    $message = Message::create($data);

    // return message as response, it will be automatically serialized to JSON
    return response($message, 201);
  }
}

Handling Events

Now, we need a way to separate our application logic, and events a really great way to deal with this. Events can be triggered when for example an action occurs in our application. We can additionally define listeners to listen to such events and carry out other type of activities.

Well, Laravel actually is really good at this, since it allows for easy definition of events and listeners out of the box. It includes various helper functions and classes to allow us to easily trigger and broadcast events.

Our new event can be created with:

php artisan make:event MessagePublished

Our event class will be created at ./app/Events, and after some editing it will look like this:

<?php

namespace App\Events;

use Symfony\Component\EventDispatcher\Event;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class MessagePublished extends Event implements ShouldBroadcast
{
    public $name;
    public $email;
    public $message;

    public function __construct($name, $email)
    {
        $this->message = "Dear {$name}, thanks for your interest. We will reply to you at {$email}";
    }

    /**
    * Get the channels the event should broadcast on.
    *
    * @return \Illuminate\Broadcasting\Channel|array
    */
    public function broadcastOn()
    {
        return [
            "contact-form"
        ];
    }
}

Well, there is a lot to explain on this one, but essentially it is good for you to know that the broadcastOn method returns the channel that we want to broadcast our event on and that the Channel class is used for broadcasting on public channels. You can read a lot more about Pusher channels on https://pusher.com/channels.

Now we need a way to dispatch our created event. In Laravel we can dispatch events using the Event Facade, or  even the event() helper.

In order to dispatch our MessagePublished event, we can edit the store  in the MessageController, and place the event call right after the message is saved, like this:

#./app/Http/Controllers/MessageController.php

use App\Events\MessagePublished;

// save message and assign return value of created message to $post array
$message = Message::create($data);

// fire MessagePublished event after DB create
event (new MessagePublished($message->name, $message->email));

Creating the Frontend

As said before, many things could be done here, but let’s keep it simple and edit our default welcome.blade.php which can be found at ./resources/views/welcome.blade.php. The content of this file could be:

<!-- ./resources/views/welcome.blade.php -->

<!DOCTYPE html>
<html lang="{{ config('app.locale') }}">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">

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

  <title>The reinvented Contact Form</title>

  <!-- Styles -->
  <link href="{{ asset('css/app.css') }}" rel="stylesheet">

  <style>
    .container {
      padding-top: 20px;
    }
  </style>

  <!-- Scripts -->
  <script>
    window.Laravel = {!! json_encode([
      'csrfToken' => csrf_token(),
    ]) !!};
  </script>
</head>
<body>

  <div id="app">
    <!-- home Vue component -->
    <contact-form></contact-form>
  </div>

  <!-- Scripts -->
  <script src="{{ asset('js/app.js') }}"></script>
</body>
</html>

As you probably have seen, mostly all of the above code is boilerplate Laravel HTML with some needed scripts and CSS files attached.

Our contact-form is actually a Vue Component that has not yet been defined. We will deal with this in a sec, so first of all, you will need to create the following folder and file structure: ./resources/assets/js/components/Home.vue

The file will have this content:

<template>
  <div class="container">
    <div class="row justify-content-center">
        <div class="jumbotron">
            <h1 class="display-4">Awesome Contact Form</h1>
            <p class="lead">This is a simple example of push notification implementation of a "Success" message using Laravel + Vue.js + Pusher</p>
        </div>
        <div class="col-sm-6 col-sm-offset-3">
            <div class="form-group">
                <label for="name">Name</label>
                <input v-model="newMsgName" id="name" type="text" class="form-control">
            </div>
                <div class="form-group">
                <label for="email">Email</label>
                <input v-model="newMsgEmail" id="email" type="email" class="form-control">
            </div>
                <div class="form-group">
                <label for="description">Description</label>
                <textarea v-model="newMsgDesc" id="description" rows="8" class="form-control"></textarea>
            </div>
            <button @click="addMessage(newMsgName, newMsgEmail, newMsgDesc)"
            :class="{disabled: (!newMsgName || !newMsgEmail || !newMsgDesc)}"
            class="btn btn-block btn-success">Submit</button>
        </div>
    </div>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        newMsgName: "",
        newMsgEmail: "",
        newMsgDesc: "",
      }
    },
    created() {
      this.listenForChanges();
    },
    methods: {
      addMessage(msgName, msgEmail, msgDesc) {
        // check if entries are not empty
        if(!msgName || !msgEmail || !msgDesc)
          return;

        // make API to save message
        axios.post('/api/message', {
          name: msgName, email: msgEmail, description: msgDesc
        }).then( response => {
          if(response.data) {
            this.newMsgName = this.newMsgEmail = this.newMsgDesc = "";
          }
        })
      },
      listenForChanges() {
        Echo.channel('contact-form')
          .listen('MessagePublished', post => {
            if (! ('Notification' in window)) {
              alert('Web Notification is not supported');
              return;
            }

            Notification.requestPermission( permission => {
              let notification = new Notification('Awesome Website', {
                body: post.message,
                icon: "https://pusher.com/static_logos/320x320.png" // optional image url
              });

              // link to page on clicking the notification
              notification.onclick = () => {
                window.open(window.location.href);
              };
            });
          })
        }
      }
    }
</script>

Analyzing the above code, the addMessage method makes a post request to our API with the required payload when a user adds a new message.

Laravel’s Echo is being used at the listenForChanges method, and it subscribes to the messages channel. This is actually the channel we are broadcasting to, from our backend. Additionally, we will listen for PostPublished events and define a callback that will activate our desktop notification whenever an event is fired.

As with all desktop notifications, we first need to request permission, then notify the user once permission is granted.

Finally, we need to define the component as a global, in our app.js:

// ./resources/assets/js/app.js

Vue.component('contact-form', require('./components/ContactForm.vue'));

3, 2…1..lift off

Now we can compile our assets using Laravel Mix:

npm run dev

And start our server with:

php artisan serve

If we now navigate to the app’s homepage at localhost:8000 (default..can be changed with –port) we should see something like this:

Where can we go next?

Well, it was a great ride, wasn’t it? And we have just scratched the surface of the great applicable examples where notifications can be handy. One of the next steps of this example could be to improve the channel notification, so it only notifies the particular user that sent the contact form and not every user.

 

 

Written by Hélio Freitas | Developer at Cleverti