---
title: "Streaming to the Browser with Livewire"
description: "See how easy Livewire makes it to broadcast events to the frontend!"
---

# Streaming to the Browser with Livewire

![streaming to the browser](https://fly.io/laravel-bytes/streaming-to-the-browser-with-livewire/assets/browser-streamin.webp)

[Annie Ruygt](https://annieruygtillustration.com/)

We’re going to use Livewire to stream some content to the browser. Livewire works best with low latency! Luckily, Fly.io lets you deploy apps close to your users. [Try it out](https://docs.fly.io/laravel), it takes just a minute.

We’re going to see how to stream content to the browser using Livewire.

🤔 What’s that mean?

Here’s an example from [Chipper CI](https://chipperci.com), which runs “builds” when you push code to git. When a command is run (perhaps `composer install`), the build output is streamed back, in chunks.

Chipper’s code stores each chunk, and sends an event that tells your browser to retrieve the latest content.

![streaming output to the browser](https://fly.io/laravel-bytes/streaming-to-the-browser-with-livewire/assets/finished-product-md.webp)

From your point of view, the content is streamed to the browser in real-time!

Let’s see how we can build the same thing.

## How it Works

There’s less magic than you may think (this is a good thing)!

**Here’s whats up:**

Imagine that some process is running a command. In our case, that command will be `composer install`.

Running `composer install` isn’t instant - it takes around 10 seconds to finish, and outputs its work as it goes.

To stream that output, we take each “chunk” of output as it’s given and “flush” each chunk (either periodically or when it gets large enough) into a queue job.

Laravel reads those queue jobs, stores the output, and broadcasts an event.

That event is received by the user’s browser, and informs the javascript to display the new content.

### The Part That We Care About

For this article, we care about the part that streams the content to the browser.

We’ll take some sample command output, split it into chunks, and dispatch an event for each chunk. The browser will respond to these events by displaying the content it has so far.

To do this, we need the server (PHP) to be able to dispatch an event that the client (the browser) will see. The common way to do this is to use websockets + Laravel [Echo](https://laravel.com/docs/9.x/broadcasting#client-side-installation) (via Laravel’s [Broadcasting](https://laravel.com/docs/9.x/broadcasting) feature).

Livewire has a great [integration with Echo](https://laravel-livewire.com/docs/2.x/laravel-echo) to make this super easy.

**Here’s what we’re going to do:**

1. Setup Laravel, and a websocket server
2. Configure Laravel to broadcast events over websockets
3. Create an event to send chunks of command output
4. Use Livewire to handle those events and display the content

## Setup Broadcasting

Setting up the websocket server is actually the most work! To avoid setting up a websocket server yourself, I suggest using [Pusher](https://pusher.com/channels), which has a great free tier.

However, if you want to just run something locally, let’s see how to use the open source [Laravel Websockets](https://beyondco.de/docs/laravel-websockets/getting-started/installation) package instead.

That project is compatible with Pusher’s API, so our code will think it’s talking to Pusher.

We’ll start by creating a Laravel project, and installing the [Laravel Websockets](https://beyondco.de/docs/laravel-websockets/getting-started/installation) package:

```
# 1. Create a Laravel project
composer create-project laravel/laravel browser-stream

cd browser-stream

# 2. Install websockets
composer require beyondcode/laravel-websockets
php artisan vendor:publish \
    --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" \
    --tag="migrations" \
    --tag="config"

# 3. Get a specific version of the Pusher SDK
composer require pusher/pusher-php-server:7.0.2

## A bug in beyondcode/laravel-websockets prevents
## us from using the latest pusher SDK
# composer require pusher/pusher-php-server

# 4. We also need Echo + Pusher client libraries
npm install --save-dev laravel-echo pusher-js
```

In addition to Pusher, Laravel Echo, and Laravel-Websockets, we’ll also install [Breeze](https://laravel.com/docs/9.x/starter-kits). Breeze gives us some view files and layout that are convenient for us to edit as a starting point.

```
# 1. Install Breeze
composer require laravel/breeze --dev
php artisan breeze:install

# 2. Migrate - Assuming your database is setup
php artisan migrate

# 3. Install and build static assets
npm install
npm run build
```

The Websocket library is [compatible with Pusher’s API](https://beyondco.de/docs/laravel-websockets/basic-usage/pusher), so let’s keep lying to our code and make it feel like Pusher is being used.

We’ll therefore set Pusher’s configuration in our `.env` file:

```
# Set to pusher
BROADCAST_DRIVER=pusher

# Farther down the default .env
# are the Pusher variables:

# Use any value for these 3
PUSHER_APP_ID=123456
PUSHER_APP_KEY=xxx
PUSHER_APP_SECRET=yyy

# Our websocket will run locally
PUSHER_HOST=127.0.0.1
PUSHER_PORT=6001
PUSHER_SCHEME=http
```

You can use **any value** you want for Pusher app ID, key, and secret. The host and port will be pointing to the Laravel Websocket server we’ll run shortly, which defaults to `localhost` at port `6001`. We’ll avoid TLS setup and use scheme `http`.

We’ll use [Echo](https://github.com/laravel/echo) on the client-side. Echo is Laravel’s library that integrates with the Broadcasting feature. This also is made to believe that we are using Pusher.

Edit file `resources/js/bootstrap.js` file to uncomment the Pusher/Echo library stuff that’s already there for you. **There’s literally no other adjustment to make** :

```
import Echo from 'laravel-echo';

import Pusher from 'pusher-js';
window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: import.meta.env.VITE_PUSHER_APP_KEY,
    wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
    wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
    wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
    forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
});
```

Then compile static assets again (via the `dev` or `build` command):

```
# This starts a dev server that
# watches for file changes
# Use "npm run build" for a one-shot build
npm run dev
```

We’re just about setup with broadcasting with Echo and “Pusher” (Laravel-Websockets). Let’s test it out!

## Create an Event

We’re going to create a new event that is broadcast to the browser via the websocket server.

The Event is a PHP class. Laravel’s Broadcasting library takes care of sending the event data to the websocket. The browser is also connected to the websocket, and thus our browser can see the event.

![browser event diagram](https://fly.io/laravel-bytes/streaming-to-the-browser-with-livewire/assets/browser-stream-diagram.webp)

Let’s make that event - it’s just a standard [Laravel event](https://laravel.com/docs/9.x/events).

We’ll have it broadcast “publicly” (using a `Channel` instead of a `PrivateChannel`) so we don’t need to mess with [channel authorization](https://laravel.com/docs/9.x/broadcasting#authorizing-channels).

```
php artisan make:event ContentUpdated
```

That generates file `app/Events/ContentUpdated.php`. We make a few adjustments to that file:

```
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

- class ContentUpdated
+ class ContentUpdated implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     *
     * @return void
     */
-    public function __construct() {}
+    public function __construct(public string $content) {}

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
-       return new PrivateChannel();
+       return new Channel('content');
    }
}
```

Here’s what was added:

1. The class implements `ShouldBroadcast` so Laravel knows to broadcast it
2. We created attribute `public string $content`
3. The class broadcasts on a public `Channel` named `'content'`

**Now we just need the frontend to do its thing.**

Breeze generated a layout file `resources/views/layouts/app.blade.php`. This file already has our static assets (CSS/JS) loading:

```
@vite(['resources/css/app.css', 'resources/js/app.js'])
```

So we should have Echo available to us already! Let’s use it.

## Fly.io ❤️ Laravel

Fly.io is a great way to run your Laravel Livewire app close to your users. Deploy globally on Fly in minutes!

[Deploy your Laravel app! →](https://docs.fly.io/laravel)

## Test Out Broadcasting

Let’s broadcast that event and see if the browser receives it.

Go ahead and edit file `resources/views/layouts/app.blade.php` and add the following near the bottom, before the closing `</body>` tag:

```
<!-- type="module" is important here -->
<script type="module">
    Echo.channel('content')
        .listen('ContentUpdated', (e) => {
            console.log(e);
        });
</script>
```

This tells `Echo` to listen on channel `content` for an event named `ContentUpdated`. It will just `console.log()` the event data.

We'read ready to test that out!

Go ahead and start the websocket server:

```
# This is a long-running process, 
# maybe run it in its own tab
php artisan websockets:serve
```

One easy way to fire the `ContentUpdated` event is to use a route.

Create a route `/fire` to dispatch the event:

```
# File routes/web.php

Route::get('/fire', function () {
    \App\Events\ContentUpdated::dispatch("more stuff");
    return "fired";
});
```

Going to that route (`localhost:8888/fire` for me) will dispatch the event. If all goes well, you should see the event logged to the console in your browser.

![logging to the console](https://fly.io/laravel-bytes/streaming-to-the-browser-with-livewire/assets/console-log.webp)

If all does not go well, be sure to check the contents of your `.env` and perhaps restart your services (re-run `npm run build`, restart your servers).

So, it works! Next we need to convert this to Livewire and then generate some content (perhaps in a console command) to pretend we have some data streaming in.

## Using Livewire

Let’s add Livewire to our project. Setting it up is just 2 steps!

First, get Livewire:

```
composer require livewire/livewire
```

Then add its static assets to `resources/views/layouts/app.blade.php`:

```
...
    @vite(['resources/css/app.css', 'resources/js/app.js'])
    @livewireStyles
</head>
<body>
    ...

    @livewireScripts
</body>
</html>
```

Livewire is now setup. We can move onto making a Livewire component:

```
php artisan livewire:make Content
```

That command generates files:

- `app/Http/Livewire/Content.php`
- `resources/views/livewire/content.blade.php`

The Blade view file `content.blade.php` is pretty simple:

```
<div 
  class="bg-gray-800 text-white p-6 rounded overflow-hidden
         font-mono text-sm whitespace-pre-wrap"
>{{ $content }}</div>
```

We added some Tailwind classes there to format the output to be a bit more like something you’d expect from running a command in a terminal.

Since we use class `whitespace-pre-wrap`, removing whitespace between the `<div>` tags and the `$content` becomes important for formatting.

Remember, we’re going to display the output from running command `composer install`. This output will have extra junk in it (ANSI color codes). Luckily there are packages we can use to help format and colorize the output:

```
composer require sensiolabs/ansi-to-html
```

We’ll use that package in the `Content.php` file to take our output and turn it into something that looks pretty:

```
<?php

namespace App\Http\Livewire;

use App\DraculaTheme;
use Livewire\Component;
use SensioLabs\AnsiConverter\AnsiToHtmlConverter;

class Content extends Component
{
    public $rawContent = '';

    public $content = 'waiting...';

    protected $listeners = [
        'echo:content,ContentUpdated' => 'appendContent'
    ];

    public function appendContent($event)
    {
        $this->rawContent .= $event['content'];

        $theme = new DraculaTheme;
        $ansiConverter = new AnsiToHtmlConverter($theme);
        $this->content = $ansiConverter->convert($this->rawContent);
    }

    public function render()
    {
        return view('livewire.content');
    }
}
```

**OK, so what’s actually happening here!?**

**First** , we have `$rawContent` and `$content`. The attribute `$content` is what the browser sees. Attribute `$rawContent` is the raw string. We transform the raw string into some HTML using the ANSI to HTML helper.

**Second** , we have `$listeners`. This is a [built-in thing in Livewire](https://laravel-livewire.com/docs/2.x/laravel-echo) to integrate with events, including Echo.

The code above listens for Echo events on the `content` channel, and specifically reacts to event `ContentUpdated`.When `ContentUpdated` is received, it calls method `appendContent()`.

**Third** , method `appendContent()` appends the latest content, and updates `$content` to valid/styled HTML content via `AnsiToHtmlConverter`.

That helper has themes! The theme file (`DraculaTheme`) is [this class](https://gist.github.com/fideloper/f36fc60f1370fdb82e29ae2d267395d5).

Now that we have this, we can remove the `<script>` tag we added to the `resources/views/layouts/app.blade.php` file. Livewire will do the work for us!

## Streaming Content

We’re not going to get into streaming content from running a command (that’d be a smallish book instead of an article).

Instead, what we’ll do fire off the `ContentUpdated` event every half-second to simulate command output being streamed into our app.

In our case, we’ll just append the content we get from the event to the browser output (we don’t need to handle the case of persisting output to permanent storage).

Let’s create a command to help is with this:

```
php artisan make:command --command content:run GenerateContentCommand
```

That will generate file `app/Console/Commands/GenerateContentCommand.php`. We can add some functionality to it:

```
<?php

namespace App\Console\Commands;

use App\Events\ContentUpdated;
use Illuminate\Console\Command;

class GenerateContentCommand extends Command
{
    protected $signature = 'content:run';

    protected $description = 'Stream sample output to the browser';

    protected $content = [
        'chunks of string content',
        'stored here within',
        'this array'
    ];

    public function handle()
    {
        collect($this->content)->each(function($chunk) {
            ContentUpdated::dispatch($chunk);
            usleep(500000); // half second
        });

        return Command::SUCCESS;
    }
}
```

The actual `$content` I used is sample command output that contains [ANSI color codes](https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#256-colors). You can see that [actual output here](https://gist.github.com/fideloper/fd2f09cdd550266d163649e2401eda41).

This takes the chunks of content (which I split up into chunks manually, roughly every 16 lines of output) and dispatches our `ContentUpdated` event with a chunk of content every half second. This simulates our application receiving content from a running process.

## Run the Command

Finally we can run the command! If you run that and watch your browser, you should see the content streamed there, live and in color!

```
php artisan content:run
```

![streaming output to the browser](https://fly.io/laravel-bytes/streaming-to-the-browser-with-livewire/assets/finished-product-md.webp)

## Review

The important thing to know here is that [Livewire has integrations with Laravel’s broadcasting features](https://laravel-livewire.com/docs/2.x/laravel-echo). Livewire can listen for dispatched events!

We setup a Laravel application, installed [Breeze](https://laravel.com/docs/9.x/starter-kits) (so we got some view files to work with), and finally installed Livewire.

We’ve setup a command that broadcasts an event every half second. The event has some content in it. Livewire listens for that event, and appends the content within it to a variable, which results in the frontend being updated with that content.

There’s not much to it, other than going through the motions of setting up Broadcasting via [Pusher](https://pusher.com/) or [Laravel-Websockets](https://beyondco.de/docs/laravel-websockets/getting-started/introduction).
