Skip to content

Streaming

Streaming allows you to send multiple server-sent events over a single HTTP connection, enabling real-time progress updates, live data feeds, and responsive long-running operations. Instead of sending one response and closing the connection, the server keeps it open and sends multiple updates as work progresses.

When to Use Streaming

Streaming is ideal for operations that:

  • Take time to complete - Processing large datasets, batch operations, imports
  • Have intermediate progress - Show progress bars, step-by-step updates
  • Generate results incrementally - Search results arriving over time
  • Need real-time updates - Live dashboards, notifications

For simple CRUD operations that complete quickly, regular responses are sufficient. Streaming shines when your users benefit from seeing progress.

Basic Streaming

stream()

Wrap your long-running operation in a stream() callback:

php
public function countdown()
{
    $time = 0;
    return hyper()
        ->view('pages.docTest', web: true)
        ->stream(function ($hyper) use ($time)  {
            set_time_limit(0);
            
            while (true) {
                try {
                    $timeLeft = 10 - $time;

                    // Stop when countdown reaches 0
                    if ($timeLeft <= 0) {
                        $hyper->forget('time')
                            ->js("showToast('The time is over','error')");
                        break;
                    }

                    $hyper->signals(['time' => $timeLeft]);
                    sleep(1);
                    $time++;

                } 
                catch (Exception $e) {
                    break;
                }

            }

            return;
        });
}

The callback receives a $stream instance that has the same methods as hyper(). Each method call sends an event immediately to the browser.

How It Works

  1. Connection Opens - Server sends SSE headers, connection stays open
  2. Callback Executes - Your code runs, calling $stream methods
  3. Events Stream - Each $stream call sends an event immediately
  4. Connection Closes - When callback finishes, connection ends

Datastar receives each event as it arrives and processes it, updating your interface in real-time.

Stream Methods

Inside the stream callback, all hyper() methods are available:

php
return hyper()->stream(function ($stream) {
    // Update signals
    $stream->signals(['status' => 'Processing...']);

    // Render fragments
    $stream->fragment('logs', 'activity-log', ['logs' => $logs]);

    // Execute JavaScript
    $stream->js('console.log("Step completed")');

    // Remove elements
    $stream->remove('#loading-spinner');

    // Any HyperResponse method works
});

Progress Tracking

Progress Bar

php
public function importContacts()
{
    return hyper()->stream(function ($stream) {
        $rows = $this->getImportData();
        $total = count($rows);

        foreach ($rows as $index => $row) {
            $contact = Contact::create($row);

            $stream->signals([
                'progress' => (($index + 1) / $total) * 100,
                'current' => $contact->name,
                'imported' => $index + 1,
                'total' => $total
            ]);
        }

        $stream->signals([
            'progress' => 100,
            'message' => "{$total} contacts imported successfully!"
        ]);
    });
}
blade
<div @signals(['progress' => 0, 'message' => '', 'current' => ''])>
    <div class="progress-bar">
        <div class="progress-fill"
             data-style:width="$progress + '%'"
             data-text="Math.round($progress) + '%'">
        </div>
    </div>

    <p data-text="$current"></p>
    <p data-text="$message" data-show="$message"></p>
</div>

Fragment Updates During Streaming

Update fragments as data becomes available:

php
public function refreshDashboard()
{
    return hyper()->stream(function ($stream) {
        // Update stats first (fast query)
        $stream->fragment('dashboard', 'stats', [
            'stats' => $this->getQuickStats()
        ]);

        // Update chart (medium query)
        $stream->fragment('dashboard', 'revenue-chart', [
            'chartData' => $this->getRevenueData()
        ]);

        // Update activity log last (slow query)
        $stream->fragment('dashboard', 'activity', [
            'activities' => $this->getRecentActivity()
        ]);

        $stream->signals(['lastRefresh' => now()->toIso8601String()]);
    });
}

Each fragment renders and updates independently as soon as the server sends it.

Exception Handling

Try-Catch in Streams

Handle errors gracefully within the stream:

php
return hyper()->stream(function ($stream) {
    try {
        $stream->signals(['status' => 'Starting import...']);

        foreach ($items as $item) {
            $this->processItem($item);
            $stream->signals(['processed' => $item->id]);
        }

        $stream->signals([
            'status' => 'Import complete!',
            'success' => true
        ]);

    } catch (\Exception $e) {
        $stream->signals([
            'status' => 'Import failed',
            'error' => $e->getMessage(),
            'success' => false
        ]);

        $stream->js('console("Import error: " . $e->getMessage(), "error")');
    }
});

Technical Details

Server-Sent Events

Streaming uses server-sent events (SSE), a standard web technology for pushing updates from server to browser. Datastar handles the SSE connection and event processing automatically.

The connection stays open while your callback executes. Each method call on $stream formats and sends an SSE event immediately.

Memory Considerations

Long-running streams should be mindful of memory:

php
return hyper()->stream(function ($stream) {
    // ✅ Process in chunks
    Product::chunk(100, function ($products) use ($stream) {
        foreach ($products as $product) {
            $this->process($product);
        }
        $stream->signals(['processed' => $product->id]);
    });

    // ❌ Load everything at once
    $allProducts = Product::all();  // Could exhaust memory
});

Session Handling

Hyper automatically closes the session before streaming starts, preventing session lock issues. This allows other requests to the same session to proceed concurrently.

Learn More