| Install | |
|---|---|
composer require dancycodes/hyper |
|
| Latest Version: | v0.1.6 |
| PHP: | ^8.2 |
Laravel Hyper integrates Datastar—a reactive hypermedia framework—with Laravel's development patterns, enabling you to build reactive user interfaces using server-side rendering and Blade templates.
Datastar provides the reactive signals system and frontend reactivity engine. Laravel provides the backend framework and development patterns. Hyper bridges these two technologies with Laravel-specific helpers, Blade directives, CSRF protection, validation integration, and file upload handling.
📚 Full Documentation at laravel-hyper.com
<!-- Reactive counter with Datastar's signals and Laravel's backend -->
<div @signals(['count' => 0])>
<button data-on:click="@postx('/increment')">+</button>
<span data-text="$count"></span>
<button data-on:click="@postx('/decrement')">-</button>
</div>
// Laravel controller with Hyper's helpers
public function increment()
{
$count = signals('count', 0);
return hyper()->signals(['count' => $count + 1]);
}
Modern web applications benefit from reactive interfaces where UI updates feel instant—users expect immediate feedback when clicking buttons, typing in search fields, or submitting forms. Achieving this responsiveness traditionally required either building separate JavaScript frontends or accepting slower full-page reloads.
Hyper offers server-driven reactivity: your Laravel application maintains control of business logic, validation, and data access while the interface updates reactively. This approach works well when your application logic belongs on the server and you want reactive UI without managing complex client-side state.
Datastar is an independent hypermedia framework that powers the reactive behavior in Hyper applications. It provides:
data-bind, data-text, data-show for reactive behavior@get, @post and other verbsAll reactivity in Hyper applications comes from Datastar. When you use data-bind, data-text, or any reactive attribute, that's Datastar handling the reactivity.
Laravel provides the server-side foundation—routing, controllers, Blade templates, validation, Eloquent, and all the patterns Laravel developers know.
Hyper connects Datastar and Laravel with:
Server-Side Helpers:
hyper() - Fluent response builder for reactive responsessignals() - Read signals sent from the frontend (similar to request())Blade Directives:
@hyper - Include Datastar JavaScript and CSRF token@signals - Initialize signals from PHP data@fragment / @endfragment - Define reusable view sectionsLaravel Integration:
@postx, @putx, @patchx, @deletex - HTTP actions with automatic CSRF tokensdata-error - Display Laravel validation errorsdata-navigate - Client-side navigation with Laravel routessignals()->validate()signals()->store()Custom Attributes:
data-for - Optimized loops for rendering collectionsdata-if - Conditional rendering based on signalsInstall Laravel Hyper via Composer:
composer require dancycodes/hyper
Laravel's package auto-discovery will register the service provider automatically.
Publish the JavaScript assets to your public directory:
php artisan vendor:publish --tag=hyper-assets
This copies Datastar and Hyper's JavaScript files to public/vendor/hyper/js/.
Add the @hyper directive to your layout's <head> section:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Application</title>
@hyper
</head>
<body>
@yield('content')
</body>
</html>
The @hyper directive includes the CSRF meta tag and loads the JavaScript module.
Signals are reactive variables. When a signal's value changes, any UI element displaying that signal updates automatically. This concept comes from Datastar.
<div @signals(['username' => '', 'email' => ''])>
<input data-bind="username" placeholder="Username" />
<input data-bind="email" type="email" placeholder="Email" />
<p>Hello, <span data-text="$username || 'Guest'"></span>!</p>
</div>
The @signals directive (Hyper) creates signals from PHP data. The data-bind and data-text attributes (Datastar) make the interface reactive.
Use CSRF-protected HTTP actions to communicate with your Laravel controllers:
<form data-on:submit__prevent="@postx('/contacts')">
<div>
<input data-bind="name" />
<div data-error="name"></div>
</div>
<div>
<input data-bind="email" type="email" />
<div data-error="email"></div>
</div>
<button type="submit">Save Contact</button>
</form>
The @postx action (Hyper) automatically includes Laravel's CSRF token. The data-error attribute (Hyper) displays validation errors.
public function store()
{
$validated = signals()->validate([
'name' => 'required|string|max:255',
'email' => 'required|email'
]);
Contact::create($validated);
return hyper()->signals([
'name' => '',
'email' => '',
]);
}
The signals() helper (Hyper) reads signals from the request. The hyper() helper (Hyper) builds the response.
📚 Learn more at laravel-hyper.com
Render Blade views to update specific sections of your page:
<!-- Your view -->
<div id="user-list">
@foreach($users as $user)
<div class="user-item">
<span>{{ $user->name }}</span>
<button data-on:click="@deletex('/users/{{ $user->id }}')">
Delete
</button>
</div>
@endforeach
</div>
public function destroy(User $user)
{
$user->delete();
$users = User::all();
return hyper()->view('users.list', compact('users'));
}
Datastar receives the rendered HTML and updates the matching element by ID.
📚 Learn more at laravel-hyper.com
Signals come in three types, each serving different purposes:
Regular Signals (sent to server with every request):
<div @signals(['count' => 0, 'email' => ''])>
<!-- These signals are sent to the server automatically -->
</div>
Local Signals (stay in browser, never sent to server):
<div @signals(['count' => 0, '_showMenu' => false])>
<!-- _showMenu stays in the browser (underscore prefix) -->
<button data-on:click="$_showMenu = !$_showMenu">Toggle Menu</button>
<nav data-show="$_showMenu">Navigation content...</nav>
</div>
Local signals are useful for UI state that doesn't need server processing—dropdowns, accordions, modal visibility, etc.
Locked Signals (protected from client tampering):
<div @signals(['userId_' => auth()->id(), 'role_' => auth()->user()->role])>
<!-- Locked signals (underscore suffix) are validated server-side -->
<!-- Client can read but cannot modify them -->
</div>
Locked signals are stored encrypted in the session and validated on every request. If a locked signal is tampered with, Hyper throws a HyperSignalTamperedException. This feature is provided by Hyper for secure state management.
📚 Learn more at laravel-hyper.com
The @signals directive offers several syntaxes:
Array syntax:
<div @signals(['count' => 0, 'message' => 'Hello'])>
<!-- Creates count and message signals -->
</div>
Variable syntax:
@php
$username = 'John';
$_editing = false;
$userId_ = auth()->id();
@endphp
<div @signals($username, $_editing, $userId_)>
<!-- Creates: username, _editing, userId_ signals -->
<!-- Variable names become signal names literally -->
</div>
Spread syntax:
<div @signals(...$user, ['errors' => []])>
<!-- If $user = ['name' => 'John', 'email' => 'john@example.com'] -->
<!-- Creates signals: name, email, errors -->
</div>
Automatic type conversion:
The @signals directive automatically converts Laravel types:
<div @signals($user)>
<!-- Eloquent Model → converted via toArray() -->
</div>
<div @signals($contacts)>
<!-- Collection → converted via toArray() -->
</div>
<div @signals($results)>
<!-- Paginator → converted via toArray() -->
</div>
In the frontend (Datastar):
<div @signals(['price' => 100, 'quantity' => 2])>
<!-- Display signal value -->
<p data-text="$price"></p>
<!-- Use in expressions -->
<p data-text="'Total: $' + ($price * $quantity)"></p>
<!-- Use in conditionals -->
<div data-show="$quantity > 0">Items in cart</div>
</div>
The $ prefix accesses signal values in Datastar expressions.
In the backend (Hyper):
public function updateCart()
{
// Get specific signal with default value
$quantity = signals('quantity', 1);
// Check if signal exists
if (signals()->has('coupon')) {
$discount = signals('coupon');
}
// Get all signals
$allSignals = signals()->all();
// Get only specific signals
$data = signals()->only(['name', 'email']);
}
From the frontend (Datastar):
<div @signals(['count' => 0])>
<button data-on:click="$count++">Increment</button>
<button data-on:click="$count--">Decrement</button>
<button data-on:click="$count = 0">Reset</button>
</div>
From the backend (Hyper):
// Update single signal
return hyper()->signals(['count' => 5]);
// Update multiple signals
return hyper()->signals([
'count' => 5,
'message' => 'Updated!',
'errors' => []
]);
// Chain with other operations
return hyper()
->signals(['count' => 5])
->view('status', $data)
->js('console.log("Done")');
Laravel's validation system integrates seamlessly with signals through Hyper's signals()->validate() method:
public function store()
{
$validated = signals()->validate([
'title' => 'required|string|max:100',
'content' => 'required|string',
'email' => 'required|email|unique:users'
]);
Post::create($validated);
return hyper()->signals(['message' => 'Post created']);
}
When validation fails, Hyper automatically:
errors signal with Laravel's error messagesdata-error attribute displays field-specific errors<form data-on:submit__prevent="@postx('/posts')">
<div>
<label>Title</label>
<input data-bind="title" />
<div data-error="title" class="text-red-500"></div>
</div>
<div>
<label>Content</label>
<textarea data-bind="content"></textarea>
<div data-error="content" class="text-red-500"></div>
</div>
<button type="submit">Create Post</button>
</form>
All Laravel validation features work with signals:
signals()->validate(
[
'email' => 'required|email|unique:users',
'password' => 'required|min:8'
],
[
'email.unique' => 'This email is already registered.',
'password.min' => 'Password must be at least :min characters.'
],
[
'email' => 'email address',
'password' => 'password'
]
);
📚 Learn more at laravel-hyper.com
File inputs bound with data-bind automatically encode files as base64 (Datastar behavior). Hyper provides validation rules and storage helpers for handling these base64-encoded files.
<form data-on:submit__prevent="@postx('/profile/avatar')">
<input type="file" data-bind="avatar" accept="image/*" />
<!-- Live preview -->
<img data-attr:src="https://raw.githubusercontent.com/dancycodes/hyper/HEAD/@fileUrl($avatar)"
class="w-32 h-32 object-cover rounded" />
<div data-error="avatar" class="text-red-500"></div>
<button type="submit">Upload Avatar</button>
</form>
Hyper provides specialized validation rules for base64-encoded files:
public function uploadAvatar()
{
signals()->validate([
'avatar' => 'required|b64image|b64max:2048|b64dimensions:min_width=200,min_height=200'
]);
$path = signals()->store('avatar', 'avatars', 'public');
auth()->user()->update(['avatar' => $path]);
return hyper()->signals(['avatar' => $path]);
}
Available validation rules:
b64file - Validates any base64-encoded fileb64image - Validates base64-encoded image filesb64max:size - Maximum file size in kilobytesb64min:size - Minimum file size in kilobytesb64mimes:ext1,ext2 - Allowed file extensionsb64dimensions:constraints - Image dimension validationDimension constraints:
'avatar' => 'b64dimensions:min_width=100,max_width=1000,ratio=16/9'
Supports: min_width, max_width, min_height, max_height, width, height, ratio
// Store with auto-generated filename
$path = signals()->store('avatar', 'avatars', 'public');
// Store and get public URL
$url = signals()->storeAsUrl('avatar', 'avatars', 'public');
// Store multiple files
$paths = signals()->storeMultiple([
'avatar' => 'avatars',
'banner' => 'banners'
], 'public');
📚 Learn more at laravel-hyper.com
Fragments let you define reusable sections within Blade views and render them independently.
<!-- resources/views/todos/index.blade.php -->
@fragment('todo-list')
<div id="todo-list">
@forelse($todos as $todo)
<div class="todo-item">
<span>{{ $todo->title }}</span>
<button data-on:click="@deletex('/todos/{{ $todo->id }}')">
Delete
</button>
</div>
@empty
<p>No todos found.</p>
@endforelse
</div>
@endfragment
@fragment('todo-count')
<div id="todo-count">
<p>{{ $todos->count() }} todos remaining</p>
</div>
@endfragment
From your controller, render specific fragments:
public function destroy(Todo $todo)
{
$todo->delete();
$todos = Todo::all();
return hyper()
->fragment('todos.index', 'todo-list', compact('todos'))
->fragment('todos.index', 'todo-count', compact('todos'));
}
By default, fragments target elements by ID. You can specify custom selectors:
return hyper()->fragment('todos.index', 'todo-list', compact('todos'), [
'selector' => '#todo-list',
'mode' => 'outer' // Replace the element selected with all content
]);
Merge modes (Datastar):
outer - Replace entire element (default)inner - Replace inner HTML onlyprepend - Add content at the beginningappend - Add content at the endbefore - Insert before elementafter - Insert after elementHyper provides client-side navigation features that work with Laravel routes.
<!-- Basic navigation -->
<a href="/dashboard" data-navigate="true">Dashboard</a>
<!-- Merge query parameters -->
<a href="/products?category=electronics" data-navigate__merge="true">Electronics</a>
<!-- Keep only specific parameters -->
<a href="/products" data-navigate__only.search,sort="true">Products</a>
<!-- Keep all except specific parameters -->
<a href="/products" data-navigate__except.page="true">Reset Page</a>
The data-navigate attribute (Hyper) enables client-side navigation without full page reloads.
// Navigate to a URL
return hyper()->navigate('/dashboard');
// Navigate with merged query parameters
return hyper()->navigateMerge('/products', ['category' => 'electronics']);
// Navigate keeping only specific parameters
return hyper()->navigateOnly('/products', ['search', 'sort']);
// Navigate excluding specific parameters
return hyper()->navigateExcept('/products', ['page']);
// Navigate with debounce
return hyper()->navigate('/search', ['debounce' => 300]);
For full page reloads (not AJAX):
// Standard Laravel redirect
return redirect('/dashboard');
// With flash data
return redirect('/dashboard')->with('success', 'User created');
📚 Learn more at laravel-hyper.com
Send continuous updates to the client using Server-Sent Events:
public function liveMetrics()
{
return hyper()->stream(function($hyper) {
while (true) {
$metrics = $this->getLatestMetrics();
$hyper->signals(['metrics' => $metrics])
->fragment('dashboard', 'metrics-card', $metrics)
->send();
sleep(5);
}
});
}
Automatically generate routes from controller methods using PHP attributes:
Enable in config/hyper.php:
return [
'route_discovery' => [
'enabled' => true,
'discover_controllers_in_directory' => [
app_path('Http/Controllers'),
],
],
];
Use attributes in controllers:
use Dancycodes\Hyper\Routing\Attributes\Route;
#[Route(middleware: 'web')]
class PostController extends Controller
{
public function index()
{
// Auto-registered as GET /post
}
#[Route(method: 'post')]
public function store()
{
// Auto-registered as POST /post/store
}
#[Route(method: 'delete', uri: '/posts/{post}')]
public function destroy(Post $post)
{
// Custom URI with route model binding
}
}
Use conditional methods to build responses dynamically:
return hyper()
->signals(['count' => $count])
->when($count > 10, function($hyper) {
return $hyper->js('showWarning()');
})
->unless($errors->isEmpty(), function($hyper) use ($errors) {
return $hyper->signals(['errors' => $errors]);
});
Execute JavaScript code from the server:
// Execute once
return hyper()->js('console.log("Task complete")');
// Execute with auto-removal
return hyper()->js('showNotification()', ['autoRemove' => true]);
// Dispatch custom DOM events
return hyper()->dispatch('task:complete', ['taskId' => 123]);
Hyper applications use Datastar's reactive attributes. Here are the most commonly used:
data-signals="{...}" - Create reactive signalsdata-computed:name="expression" - Create computed signaldata-effect="expression" - Run code when signals changedata-text="$signal" - Display signal value as textdata-bind="signal" - Two-way bind input to signaldata-show="condition" - Show/hide elementdata-class:name="condition" - Toggle CSS classdata-attr:name="value" - Set attribute valuedata-on:click="expression" - Handle click eventsdata-on:input="expression" - Handle input eventsdata-on:submit="expression" - Handle form submitdata-on:[event]="expression" - Handle any DOM eventEvent modifiers:
data-on:click__prevent - Prevent defaultdata-on:submit__prevent__stop - Prevent default and stop propagationdata-on:input__debounce.300ms - Debounce input@get('/url') - GET request@post('/url') - POST request@put('/url') - PUT request@patch('/url') - PATCH request@delete('/url') - DELETE requestHyper's CSRF-protected actions:
@postx('/url') - POST with CSRF token@putx('/url') - PUT with CSRF token@patchx('/url') - PATCH with CSRF token@deletex('/url') - DELETE with CSRF tokendata-ref="myRef" - Create reference to elementdata-intersect="expression" - Run when element enters viewportFor complete Datastar documentation, visit data-star.dev.
📚 For Hyper-specific attributes and Laravel integration, visit laravel-hyper.com
Run the test suite:
composer test
Hyper includes tests covering:
Hyper works well for server-driven applications with reactive UI requirements:
Hyper maintains Laravel's server-side architecture while providing reactive user experiences.
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
composer installcomposer testSecurity vulnerabilities should be reported privately. Please review SECURITY.md for responsible disclosure procedures.
Laravel Hyper is made possible by:
Laravel Hyper is open-source software licensed under the MIT license.
Made with ❤️ for the Laravel community