Quickstart

Build a complete task list with server-side validation in under 10 minutes.

Note
This guide assumes you've completed the Installation. If not, do that first.

What We're Building

We'll create a task list that demonstrates the core Gale patterns:

  • State updates — Change Alpine data from your Laravel controllers
  • Form validation — Server-side rules with reactive error messages
  • DOM manipulation — Add and remove HTML without page reloads
  • Loading states — Show feedback during server requests

By the end, you'll understand the patterns used in every Gale application.

Step 1: Create the Route

Add a simple route to display our task list. In routes/web.php:

1use Illuminate\Support\Facades\Route;
2
3Route::get('/tasks', fn () => view('tasks'));

Step 2: Create the View

Create resources/views/tasks.blade.php:

 1<!DOCTYPE html>
 2<html lang="en">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 6    <title>Task List</title>
 7    @gale
 8    <script src="https://cdn.tailwindcss.com"></script>
 9</head>
10<body class="bg-gray-100 min-h-screen py-8">
11
12<div x-data="{ title: '' }"
13     class="max-w-md mx-auto bg-white rounded-lg shadow p-6">
14
15    <h1 class="text-2xl font-bold mb-4">Task List</h1>
16
17    <!-- Add Task Form -->
18    <form @submit.prevent="$postx('/tasks')" class="mb-6">
19        <div class="flex gap-2">
20            <input type="text"
21                   x-model="title"
22                   placeholder="Add a task..."
23                   class="flex-1 px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500">
24            <button type="submit"
25                    x-loading.attr="disabled"
26                    x-loading.class="opacity-50"
27                    class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
28                Add
29            </button>
30        </div>
31        <p x-message="title" class="text-red-500 text-sm mt-1"></p>
32    </form>
33
34    <!-- Task List -->
35    <ul id="task-list" class="space-y-2">
36        <!-- Tasks will be added here -->
37    </ul>
38
39</div>
40
41</body>
42</html>

Breaking Down the View

Let's examine the key Gale features in this template:

The @gale Directive

Loads Alpine.js, the Alpine Morph plugin, and the Gale plugin. It also outputs the CSRF meta tag that Gale uses for secure requests.

The x-data State

Standard Alpine.js. We define title for the form input. Gale sends this entire object with each request.

The $postx() Magic

Gale's HTTP magic. The x suffix means "with CSRF protection". When the form submits, Gale serializes the Alpine state and POSTs it to /tasks.

The x-loading Directive

Shows loading feedback during requests. x-loading.attr="disabled" disables the button, and x-loading.class="opacity-50" adds a visual cue.

The x-message Directive

Displays validation errors from the server. When Laravel validation fails, Gale automatically manages a global messages state and this directive shows the error for the specified field.

Step 3: Handle Adding Tasks

Now add the POST route that validates input and adds tasks. In routes/web.php:

 1Route::post('/tasks', function () {
 2    // Validate the incoming state
 3    $validated = request()->validateState([
 4        'title' => 'required|string|min:3|max:255',
 5    ]);
 6
 7    // Generate a unique ID for this task
 8    $taskId = uniqid('task-');
 9
10    // Build the HTML for the new task
11    $html = '
12        <li id="' . $taskId . '" class="flex items-center gap-2 p-2 bg-gray-50 rounded">
13            <span class="flex-1">' . e($validated['title']) . '</span>
14            <button @click="$postx(\'/tasks/' . $taskId . '/delete\')"
15                    class="text-red-500 hover:text-red-700">
16                Delete
17            </button>
18        </li>
19    ';
20
21    return gale()
22        ->state('title', '')             // Clear the input
23        ->append('#task-list', $html);  // Add the task to the list
24});

What's Happening Here

1

request()->validateState()

Validates the Alpine state sent from the frontend. If validation fails, Gale automatically returns the error messages to Gale's global messages state, which x-message reads from.

2

gale()

Returns the GaleResponse builder. This is your interface for sending updates back to the browser via Server-Sent Events.

3

->state('title', '')

Updates the title property in Alpine's state, clearing the form input.

4

->append('#task-list', $html)

Appends the new task HTML to the element matching the selector. The browser receives this and Alpine reactively updates the DOM.

Step 4: Handle Deleting Tasks

Add the delete route:

1Route::post('/tasks/{taskId}/delete', function ($taskId) {
2    return gale()->remove('#' . $taskId);
3});

The remove() method tells the browser to remove the element matching the CSS selector. Clean and simple.

Step 5: Test It

Visit /tasks in your browser and try:

  • Submitting an empty form (see validation errors appear)
  • Entering a task shorter than 3 characters
  • Adding valid tasks (watch them appear instantly)
  • Deleting tasks (watch them disappear)
It works!
Open your browser's Network tab and filter by "fetch" to see the SSE responses streaming back from your server.

How It All Works

Here's the complete request/response cycle when you add a task:

Frontend (Alpine Gale)

  1. 1 User types in input, Alpine updates title
  2. 2 Form submits, $postx() serializes state
  3. 3 POST request sent with JSON body + CSRF
  4. 6 SSE events received, state & DOM updated
  5. 7 Alpine reactivity triggers UI refresh

Backend (Laravel Gale)

  1. 4 Route receives request, validates state
  2. 5 gale() builds SSE response with updates

The key insight: your Laravel code decides what changes, and Alpine automatically applies them. You never write JavaScript to handle responses—Gale does that for you.

Complete Code

Here's everything together for easy copying:

routes/web.php

 1use Illuminate\Support\Facades\Route;
 2
 3// Display the task list
 4Route::get('/tasks', fn () => view('tasks'));
 5
 6// Add a new task
 7Route::post('/tasks', function () {
 8    $validated = request()->validateState([
 9        'title' => 'required|string|min:3|max:255',
10    ]);
11
12    $taskId = uniqid('task-');
13
14    $html = '
15        <li id="' . $taskId . '" class="flex items-center gap-2 p-2 bg-gray-50 rounded">
16            <span class="flex-1">' . e($validated['title']) . '</span>
17            <button @click="$postx(\'/tasks/' . $taskId . '/delete\')"
18                    class="text-red-500 hover:text-red-700">
19                Delete
20            </button>
21        </li>
22    ';
23
24    return gale()
25        ->state('title', '')
26        ->append('#task-list', $html);
27});
28
29// Delete a task
30Route::post('/tasks/{taskId}/delete', function ($taskId) {
31    return gale()->remove('#' . $taskId);
32});

resources/views/tasks.blade.php

 1<!DOCTYPE html>
 2<html lang="en">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 6    <title>Task List</title>
 7    @gale
 8    <script src="https://cdn.tailwindcss.com"></script>
 9</head>
10<body class="bg-gray-100 min-h-screen py-8">
11
12<div x-data="{ title: '' }"
13     class="max-w-md mx-auto bg-white rounded-lg shadow p-6">
14
15    <h1 class="text-2xl font-bold mb-4">Task List</h1>
16
17    <!-- Add Task Form -->
18    <form @submit.prevent="$postx('/tasks')" class="mb-6">
19        <div class="flex gap-2">
20            <input type="text"
21                   x-model="title"
22                   placeholder="Add a task..."
23                   class="flex-1 px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500">
24            <button type="submit"
25                    x-loading.attr="disabled"
26                    x-loading.class="opacity-50"
27                    class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
28                Add
29            </button>
30        </div>
31        <p x-message="title" class="text-red-500 text-sm mt-1"></p>
32    </form>
33
34    <!-- Task List -->
35    <ul id="task-list" class="space-y-2">
36        <!-- Tasks will be added here -->
37    </ul>
38
39</div>
40
41</body>
42</html>

Key Concepts Summary

Concept Frontend Backend
HTTP Requests $postx(), $get() Standard Laravel routes
State Access Alpine's x-data request()->state()
State Updates Automatic from SSE gale()->state()
Validation x-message directive request()->validateState()
DOM Updates Automatic morphing append(), remove(), view()
Loading States x-loading directive N/A (automatic)

Next Steps

You've learned the fundamental patterns of Gale. Here's where to go deeper:

On this page