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:
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
- Connection Opens - Server sends SSE headers, connection stays open
- Callback Executes - Your code runs, calling
$streammethods - Events Stream - Each
$streamcall sends an event immediately - 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:
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
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!"
]);
});
}<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:
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:
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:
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
- Response Builder - Methods available in streams
- Fragments - Updating fragments during streaming
- Performance - Optimizing stream performance

