Creating a Middleware to Globally Log Submissions in Livewire

Baggages pass through a scanner machine.
Image by Annie Ruygt

Need a quick Laravel app on the cloud? Deploy your Laravel application on Fly.io, you’ll be up and running in minutes!

Ever implemented logic that applied to multiple Livewire components? Why not use a middleware to make it available across Livewire components?

For example: Imagine having two Livewire components that handle two different form submissions. If, say, each of these components handle their form submissions through a submit() method, how can we easily log the submissions they receive?

We might do something like log the submission details in the submit() method of each component, right before processing the submission. But imagine having more than two components of —say three, five, or more? That’s going to be a ton of repetitive logging.

In this post, instead of repeating logic in each component’s submit() method, we’ll apply our logic in a single middleware to make it available for Livewire components that trigger a submit() method.

Version Notice. This article focuses on Livewire v2, details for Livewire v3 would be different though!

Let’s start

To start, let’s set up a simple Livewire component that handles a form submission. It will take in a name and email, and save it to the database.

Create the component with php artisan make:livewire ContactForm and add the following view:

{{-- resources/views/livewire/contact-form.blade.php --}}
<div>
  <form wire:submit.prevent="submit">
    <input type="text" wire:model.defer="name">
    @error('name') <span class="error">{{ $message }}</span> @enderror

    <input type="text" wire:model.defer="email">
    @error('email') <span class="error">{{ $message }}</span> @enderror

    <button type="submit">Save Contact</button>
  </form>
</div>

The form is wired to the submit() method, while each of the text input field are wire:model.defer-ed to their respective public properties in the component:

/* app\Http\Livewire\ContactForm.php */

class ContactForm extends Component
{
  public $name;
  public $email;

  public function submit()
  {
    \App\Models\Contact::create([
      'name' => $this->name,
      'email' => $this->email
    ]);
  }

}

The only time a request is made to the Livewire component’s route is during form submission, which calls the component’s submit() method. This is how it looks like:


  Two screenshots of the network inspector tab in a browser. 
  The first screenshot shows the url of the request made due to the form submission in the Livewire component. 
  The second shows the payload of the request, showing an `updates` attribute that contains three items. 
    The first and second items `syncInput` updates to `name` and `email`. The last item a `callMethod` updates to the `submit` method.

The request is sent to livewire/message/contact-form, and we can see three items in its updates payload. One syncInput type for each wired text input field, containing values for name and email, and finally a callMethod type to trigger a call to the submit() method of the component.

Logging Requests for Submit Method

Let’s say, we want to log submission details made through the submit() method of our Livewire component. Create a middleware with php artisan make:middleware LogInputRequests:

/* App\Http\Middleware\LogInputRequests */
use Log;

class LogInputRequests
{
  public function handle(Request $request, Closure $next): Response
  {
      // 1. Make sure Livewire's updates key exists
      if( isset($request->updates) ){
        foreach( $request->updates as $update ){   

            // 2.Find target "submit" method
            if( 
                isset($update['payload']['method']) && 
                $update['payload']['method'] == 'submit' 
            ){
                $userId = $request->user()? $request->user()->id : 'NA';

                // 3. Log Stuff
                Log::info( 
                  "User: $userId, Path: $request->path(), 
                  Submission:". json_encode($request->updates) 
                );
                break;
            }

        }
      }

      // Proceed
      return $next($request);
  }
}

The above middleware processes requests specific to Livewire components. Of course, since it’s handling requests to Livewire components, we have to be a bit mindful in creating this middleware. When creating middlewares that handle Livewire component requests, we generally have to answer two questions:

The first question is, when should this middleware be applied? Livewire relies on configured middleware groups that’s applied on all Livewire components. This means we’ll have to specify conditions that will check whether to apply the middleware or not. Otherwise, it will run its logic for all Livewire components. And, do we want that?—no, not in this use case.

So, notice above, we check for the existence of a submit method in the request’s updates key. This key is Livewire’s magical bag that let’s it know about changes in the view that the server needs to sync, or trigger methods with. We check for the submit method because we want to log submission details, which are processed through this method. And, notice, we do not limit this to one specific component. Rather, it is applicable for any, so long as the submit method was triggered. This allows us to log submission for not just our component above, but for other components triggering a submit() method as well.

The second question is, what part of the Livewire request do we include in the intended logic? In our case, we want to log submission details. Submission details would of course be found in the updates bag, so we simply log the updates bag which will contain values for name and email, as well as the call to the submit() method. Of course, we can further parse this to our liking or use case.

And speaking of parsing—please remember: We’re logging user input here. Make sure, and do not forget, to sanitize this.

Fly.io ❤️ Laravel

Need a place in the clouds to fly your app? Deploy your servers close to your users with Fly.io, it’ll be up in minutes!

Deploy your Laravel app!

Registering the Middleware in Livewire config

Livewire’s config file config/livewire.php allows us to register middleware groups in its middleware_group attribute.

In order to apply our middleware logger to requests made on our Livewire components, we create a new middleware group in app/Http/Kernel.php, where we include our middleware class:

/* app/Http/Kernel.php */

protected $middlewareGroups = [
  // other middleware groups
  'for_livewire'=>[
      \App\Http\Middleware\LogInputRequests::class,
  ]

Then, we pull Livewire’s config file into our project with php artisan livewire:publish --config, and include our recently declared middleware group here:

/* config/livewire.php */
'middleware_group' => ['web','for_livewire'],

Every request made to a Livewire component via its route livewire/message/ will now pass through our middleware. Of course, thanks to our logic of checking for the submit method, only requests containing the submit method in the updates key will be logged.

So now, if we click on the submit button of our Livewire component, the submission details should now be logged, likeso:


  A screenshot of the log of the request made from triggering the contact-form's `submit()` method. 
  The following log can be seen: "User: 1, Path: livewire/message/contact-form", and a "Submission" value that shows three updates item: two syncInput items for name and email, 
  and a callMethod item for the submit method.

And, just like that—with one middleware—we have our instant submission logger, globally available to any Livewire component—neat!

Conclusion

There are many other use cases for implementing logic into middleware, just like applying policies to safeguard access to your Livewire components.

Moving logic into middleware makes sense when that logic is repeated in multiple, different places. What’s more, Livewire automatically applies one Livewire-valid middleware to all Livewire components, making application of Livewire-wide logic a convenient task to do with middlewares.

Do you have any other Livewire-wide logic to implement, if so, why not implement it into a middleware?