Skip to main content

2 posts tagged with "php"

View All Tags

Streaming AI Responses with Laravel and Ressonance

· 4 min read
Leonardo Lemos
Laravel FullStack Developer, Websockets Specialist

Token-by-token AI output — the little "typing" effect you get from ChatGPT — isn't a nice-to-have anymore, it's the expected UX for anything that talks to an LLM. And the wrong way to build it is polling an endpoint every few hundred milliseconds hoping the next chunk is ready.

The demo is a Laravel application using Ressonance but this can be achieved with any other language or any other reverb/pusher compatible websocket provider.

Prerequisites

  • A Laravel app with Laravel AI installed and at least one provider configured in config/ai.php (OpenAI, Anthropic, Ollama — any of them work, streaming isn't provider-specific).
  • A free Ressonance app (it's a drop-in Pusher-protocol replacement, so setup is just grabbing your app key/secret/host).

Step 1 — Point broadcasting at Ressonance

Ressonance speaks the same protocol as Laravel Reverb, so there's no new driver to install — you configure the reverb connection in config/broadcasting.php and point it at Ressonance's host instead of your own server:

'reverb' => [
'driver' => 'reverb',
'key' => env('REVERB_APP_KEY'),
'secret' => env('REVERB_APP_SECRET'),
'app_id' => env('REVERB_APP_ID'),
'options' => [
'host' => env('REVERB_HOST'),
'port' => env('REVERB_PORT', 443),
'scheme' => env('REVERB_SCHEME', 'https'),
'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
],
],

And in .env, use the credentials from your Ressonance app dashboard:

BROADCAST_CONNECTION=reverb
REVERB_APP_ID=your-app-id
REVERB_APP_KEY=your-app-key
REVERB_APP_SECRET=your-app-secret
REVERB_HOST="your-app.ressonance.com"
REVERB_PORT=443
REVERB_SCHEME=https

VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

That's the entire backend infrastructure setup. No server to provision, no process to keep alive.


Step 2 — Install and configure Laravel Echo

npm install --save-dev laravel-echo pusher-js

Create resources/js/echo.js:

import Echo from 'laravel-echo';

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

window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});

Then import it from your main entry point, resources/js/app.js:

import './echo';

Why pusher-js if we're not using Pusher?

Ressonance and Reverb both implement the Pusher protocol, and laravel-echo's reverb broadcaster is built on top of the pusher-js client under the hood. You never talk to Pusher's servers — the wsHost config is what actually decides where the socket connects.


Step 3 — Broadcast AI tokens as they're generated

Laravel AI's agent()->stream() yields a sequence of stream events as the model responds. Each token arrives as a TextDelta event, and it exposes a broadcast() method — hand it a channel and it goes straight out over the wire:

use Illuminate\Broadcasting\Channel;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Streaming\Events\TextDelta;

use function Laravel\Ai\agent;

class TestingController extends Controller
{
public function index()
{
$stream = agent()->stream('Tell me a short story', provider: Lab::Ollama);

foreach ($stream as $event) {
if ($event instanceof TextDelta) {
$event->broadcast(new Channel('new-text-delta'));
}
}

return '';
}
}

Why check instanceof TextDelta?

A stream isn't only text — depending on what the agent is doing, you can also get events for tool calls, message boundaries, and so on. Filtering to TextDelta means you only broadcast the actual token chunks, not the plumbing around them.


Step 4 — Listen for tokens in the browser

First, make sure the compiled assets are actually loaded — add @vite to your view's <head>:

@vite('resources/js/app.js')

Then drop a text area to render into, and listen on the same channel you broadcast to:

<textarea id="stream-output" rows="20" readonly></textarea>

<script>
document.addEventListener("DOMContentLoaded", () => {
const streamOutput = document.getElementById('stream-output');

Echo.channel('new-text-delta')
.listen('.text_delta', (e) => {
streamOutput.value += e.delta;
});
});
</script>

Why the leading dot in .text_delta?

TextDelta::broadcast() sets a custom event name (text_delta) instead of relying on Laravel's default namespaced event name. Echo assumes any event name is namespaced under App.Events unless you tell it otherwise — the leading dot says "this is a raw event name, don't prefix it." Without it, Echo listens for an event that never arrives.

Why hand this off to Ressonance

Everything above works exactly the same way against a self-hosted Reverb server — Ressonance doesn't change a line of the code. What it removes is running that server yourself: no process to keep alive, no scaling it when concurrent streams spike, no TLS termination to configure. Since it speaks the same Pusher protocol Echo already expects, swapping in Ressonance is a config change, not a rewrite.

If you're already prototyping AI streaming locally and want it production-ready without standing up your own WebSocket infrastructure, Ressonance is built for exactly this.

Building a Real-Time Dashboard with Laravel and WebSockets

· 8 min read
Victor Gazotti
Laravel FullStack Developer

Real-time features are no longer a “nice to have.” Dashboards, notifications, live metrics, and collaborative tools are now expected behaviors in modern applications.

As Laravel developers, we’re in a great position: the ecosystem offers first-class support for WebSockets through Laravel Echo and Reverb. Still, many tutorials stop at theory or overused chat examples.

In this Laravel WebSockets tutorial, I want to share a practical, production-adjacent example: a real-time dashboard that updates totals instantly, without polling, using Echo and Reverb.

This is a hands-on guide aimed at developers who want to understand how WebSockets actually work in Laravel, not just copy-paste code.