Introduction to Turbo Laravel
2.xturbo-rails gem and aims to streamline the implementation of Turbo features in the Laravel ecosystem.repository·2.x·Indexed 21 days ago
https://github.com/hotwired-laravel/turbo-laravelA package providing conventions and tools to integrate Hotwire (Turbo) into Laravel applications, inspired by the turbo-rails gem. It includes features for Turbo Stream broadcasting via Laravel Echo and Reverb, automated model broadcasting using the Broadcasts trait, and specialized response builders and Blade components for seamless Hotwire integration.
turbo-rails gem and aims to streamline the implementation of Turbo features in the Laravel ecosystem.Turbo intercepts form submissions as fetch requests. For non-GET submissions, the server must return a redirect response with a 303 See Other status code. Turbo follows this redirect and renders the resulting response.
Laravel Integration:
When TurboMiddleware is active, Laravel's redirect() helper automatically converts 302 redirects to 303 for Turbo requests, ensuring compatibility.
Validation Errors:
For validation errors, return a 422 Unprocessable Entity status with the form HTML. Turbo Laravel automatically handles redirecting to the appropriate form resource (e.g., mapping *.update to *.edit) when the form route exists.
Although Turbo.js can automatically handle CSRF tokens via the csrf-token meta tag, it is a best practice to still include the @csrf Blade directive within your HTML forms. This ensures that forms remain functional for users who have JavaScript disabled, as the form will contain the necessary token for a standard synchronous POST request.
<form action="{{ route('chirps.store') }}" method="post">
@csrf
<!-- ... -->
</form>When using Turbo Frames, a standard Laravel "redirect back" on validation failure can cause forms to disappear if the form was injected into a page that doesn't initially render it. Turbo expects a non-200 HTTP status code (typically 422 Unprocessable Entity) containing the form and error messages in the response body, rather than a redirect.
Turbo Laravel solves this via TurboMiddleware, which intercepts ValidationException. It attempts to guess the correct form-rendering route and performs an internal request to fetch the form with the correct status code.
The middleware relies on Laravel's resource route naming conventions to guess the form's location:
.store $\rightarrow$ guesses .create (e.g., posts.comments.store $\rightarrow$ posts.comments.create).update $\rightarrow$ guesses .edit (e.g., comments.update $\rightarrow$ comments.edit).destroy $\rightarrow$ guesses .deleteIf the guessed route does not exist, the middleware falls back to Laravel's default "redirect back" behavior.
When passing a model instance directly to turbo_stream($model), the helper automatically selects an action based on the model's recent state:
append action (can be overridden as the second argument).replace action (can be overridden as update).remove action.// Override default action for a newly created model
return turbo_stream($comment, 'append');Turbo Laravel integrates Turbo technologies with Laravel to enable partial page updates and real-time interactions using the 'HTML Over the Wire' philosophy. Instead of sending JSON, the server sends HTML, allowing the server to handle rendering while keeping the client-side logic minimal. It relies on four core techniques:
<body> without a full page reload.<turbo-frame> elements.data-controller, data-action, and data-target attributes.When using InteractsWithHotwireNativeNavigation helpers, calling ->with() on the redirect response behaves differently depending on the client:
/recede_historical_location?status=Success).This allows the native client to intercept the redirect, read the query parameters, and display native UI elements like toasts.
return $this->recedeOrRedirectTo(route('trays.show', $tray))
->with('status', __('Tray created.'));Turbo Drive accelerates navigation by intercepting link clicks and form submissions and converting them into fetch requests. Instead of a full browser reload, Turbo replaces the <body> and merges the <head> of the new page. This allows the window, document, and <html> elements to persist, maintaining the JavaScript environment and providing SPA-like speed with server-rendered HTML.
Turbo.visit(). An advance visit pushes a new entry to the browser history, while a replace visit modifies the current history entry.When using Turbo Stream Model Broadcasts (an optional feature), the package attempts to automatically find the correct partial for a model.
Requirements for Model Partials:
camelCase (e.g., a Comment model expects a $comment variable).Broadcasting Channel Naming:
By default, you can use the model's Fully Qualified Class Name (FQCN) as the broadcasting channel authorization route with a wildcard. For a Comment model in App\Models\, the channel would be App.Models.Comment.{comment}.
Instead of manually hooking into Eloquent events, you can automate broadcasting by defining a $broadcasts property on your model. This instructs the Broadcasts trait to automatically trigger broadcasts on model events (created, updated, deleted).
Setting $broadcasts = true enables default broadcasting (e.g., append on creation).
You can pass an array to $broadcasts to customize behavior:
insertsBy: Set to 'prepend' or 'append' to change how new models are handled.stream: Set a custom name for the Turbo Stream tag.protected $broadcasts = [
'insertsBy' => 'prepend',
'stream' => 'my-comments',
];Use $broadcastsTo to define which channel(s) the broadcasts should be sent to. This can be a relationship name or an array of relationships:
protected $broadcastsTo = 'post'; // Sends to the 'post' relationship's channelAlternatively, implement a broadcastsTo() method for more complex logic, returning a model, a Channel instance, or an array of them.
class Comment extends Model
{
use Broadcasts;
protected $broadcasts = [
'insertsBy' => 'prepend',
];
protected $broadcastsTo = 'post';
public function post()
{
return $this->belongsTo(Post::class);
}
}You can automatically trigger page refreshes for users listening to a model's channel when that model changes. This is useful for keeping a UI in sync with the current state of a resource.
Set protected $broadcastsRefreshes = true; to trigger a refresh broadcast on all relevant events (created, updated, deleted).
Use protected $broadcastsRefreshesTo to specify which related model's channel should receive the refresh signal. This can be a relationship name or an array of relationships.
protected $broadcastsRefreshes = true;
protected $broadcastsRefreshesTo = ['post'];Alternatively, implement a broadcastsRefreshesTo() method to return a model, a channel name string, or a Channel instance.
class Comment extends Model
{
use Broadcasts;
protected $broadcastsRefreshes = true;
public function broadcastsRefreshesTo()
{
return [$this->post];
}
}You can instruct Turbo to use different page refresh strategies using meta tags. While the default behavior is to replace the <body> content, Turbo 8+ supports 'morphing' the page to preserve state and improve perceived performance.
In a standard HTML environment, use these meta tags:
<meta name="turbo-refresh-method" content="morph">
<meta name="turbo-refresh-scroll" content="preserve">Turbo Laravel Integration: To make this more developer-friendly and enable autocomplete, use the provided Blade component:
<x-turbo::refreshes-with method="morph" scroll="preserve" /><x-turbo::refreshes-with method="morph" scroll="preserve" />