How to create a simple event streaming in Laravel?
Tags: laravel, materialize, database, javascript
Published: 2022-01-17 15:55:48

## Introduction

Event streams provide you with a way to send events to the client without having to reload the page. This is useful for things like updating the user interface in real-time changes are made to the database.

Unlike traditional Long-polling using AJAX requests, where multiple requests are sent to the server and a new connection is established each time, event streams are sent to the client in real-time in a single request.

![Server sent events](https://imgur.com/51pLy3q.png)

In this article, I will show you how to create a simple event streaming in Laravel.

## Prerequisites

Before you start, you need to have Laravel installed on your machine.

I will be using a DigitalOcean Ubuntu Droplet for this demo. If you wish, you can use my affiliate code to get [free $100 DigitalOcean credit](https://m.do.co/c/2a9bba940f39) to spin up your own servers!

If you do not have that yet, you can follow the steps from this tutorial on how to do that:

- [How to Install Laravel on DigitalOcean with 1-Click](https://devdojo.com/bobbyiliev/how-to-install-laravel-on-digitalocean-with-1-click)

Or you could use this awesome script to do the installation:

- [LaraSail](https://devdojo.com/episode/laravel-on-digital-ocean-with-larasail)

## Creating a controller

Let's start by creating a controller that will handle the event stream.

To do so, we will use the following command:

```
php artisan make:controller EventStreamController
```

This will create a new controller in the `App\Http\Controllers` directory.

## Adding the event stream method

Once we have our controller created, we need to add the `stream` method to it. The method will be used to send the event stream.

Open the `EventStreamController.php` file and add the following code:

```php
<?php

namespace App\Http\Controllers;

use Carbon\Carbon;
use App\Models\Trade;

class StreamsController extends Controller
{
    /**
     * The stream source.
     *
     * @return \Illuminate\Http\Response
     */
    public function stream(){
        return response()->stream(function () {
            while (true) {
                echo "event: ping\n";
                $curDate = date(DATE_ISO8601);
                echo 'data: {"time": "' . $curDate . '"}';
                echo "\n\n";

                $trades = Trade::latest()->get();
                echo 'data: {"total_trades":' . $trades->count() . '}' . "\n\n";

                $latestTrades = Trade::with('user', 'stock')->latest()->first();
                if ($latestTrades) {
                    echo 'data: {"latest_trade_user":"' . $latestTrades->user->name . '", "latest_trade_stock":"' . $latestTrades->stock->symbol . '", "latest_trade_volume":"' . $latestTrades->volume . '", "latest_trade_price":"' . $latestTrades->stock->price . '", "latest_trade_type":"' . $latestTrades->type . '"}' . "\n\n";
                }

                ob_flush();
                flush();

                // Break the loop if the client aborted the connection (closed the page)
                if (connection_aborted()) {break;}
                usleep(50000); // 50ms
            }
        }, 200, [
            'Cache-Control' => 'no-cache',
            'Content-Type' => 'text/event-stream',
        ]);
    }
}
```

The main things to note here are:
- We are using the `response()->stream()` method to create the event stream.
- Then we have an infinite loop that sends the event stream every 50ms.
- We are using the `ob_flush()` and `flush()` methods to send the event stream.
- We are using the `sleep()` method to wait for 50ms before sending the next event.
- We are using the `connection_aborted()` method to break the loop if the client aborted the connection.
- We are using the `Carbon\Carbon` class to get the current date.
- We are using the `App\Models\Trade` model to get the latest trades. This is just for the demo, you can use any model you want.
- The `Content-Type` header is set to `text/event-stream` to tell the browser that the response is an event stream.

## Enable output buffering

For the above code to work, we need to enable output buffering in your PHP.ini file. This is done by adding the following line to the `php.ini` file:

```
output_buffering = On
```

You may need to reload the PHP-FPM service after making this change. Or if you are using Apache, you can restart Apache.

## Adding the routes

We would like to call the `stream` method when the `/stream` route is requested.

The route will be added to the `routes/web.php` file and will look like this:

```php
use App\Http\Controllers\StreamsController;

Route::get('/stream', [StreamsController::class, 'stream']);
```

## Working with the event stream on the frontend

You could use a frontend framework like [Vue.js](https://vuejs.org/) to handle the event stream. But for this demo, I will use pure Javascript.

The JavaScript snippet that I will add to my blade template will look like this:

```javascript
const eventSource = new EventSource('/stream');

eventSource.onmessage = function(event) {
    const data = JSON.parse(event.data);
    if (data.time) {
        document.getElementById('time').innerHTML = data.time;
    }
    const newElement = document.createElement("li");
    const eventList = document.getElementById("list");

    newElement.textContent = "message: " + event.data;
    eventList.appendChild(newElement);
}
```

To see this in action, you can try the following demo!

## Demo project

If you want to see how the event stream works, you can check out the demo project I created:

[Laravel EventStream: Real-time stock trades dashboard with Laravel and Materialize](https://github.com/bobbyiliev/materialize-tutorials/tree/main/laravel-eventstreams)

The demo project does not only show the event stream but also has a simple frontend dashboard and uses [Materialize](https://materialize.com) as a streaming database.

![Laravel EventStream](https://user-images.githubusercontent.com/21223421/149408173-7a76c837-314b-419a-a589-d6867fc55c99.gif)

## SSE vs WebSockets

Event streams are great and easy to use, but there are some pros and cons when compared to other streaming protocols like [WebSockets](https://en.wikipedia.org/wiki/WebSocket).

For example, SSE is unidirectional, meaning that once the connection is established the server will only send data to the client, but the client can't send data back to the server.

Unlike long-polling, with WebSockets you only have a single connection to the server similar to SSE (Server-Sent Events). The connection is duplex, meaning you can send and receive data from the server.

If you want to learn more about the differences between SSE and WebSockets, check out this great video by Martin Chaov:

{% youtube n9mRjkQg3VE %}

## Conclusion

For more information about event streams, check out this documentation here by Mozilla:

- [Event streams in the Web Platform](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)

There you would find a more in-depth explanation of the event streams and how they work.

For more information about Materialize, check out this video here:

{% youtube WVORbibyFGA %}

Hope you enjoyed this tutorial!