Skip to content

Chapter 5.6: Displaying Data via Blade + Fetch

Study Time: 50 minutes


1. Hybrid Approach: The Best of Both Worlds

We can build a page in two ways:

  1. Full Server-Side Rendering (SSR): Laravel generates the entire HTML, including the list of planets. For any update (deletion, addition), the page reloads completely.
  2. Full Client-Side Rendering (CSR): Laravel serves an empty HTML "shell," and JavaScript requests all data from the API and renders it on the client. (This is the Single Page Application - SPA - approach).

Our choice is a hybrid approach:

  • Initial Load (SSR): Laravel immediately serves the page with the list of planets already rendered. This is fast and good for SEO. The user sees the content instantly.
  • Subsequent Actions (CSR): JavaScript intercepts user actions (button clicks) and interacts with the API, updating only the necessary parts of the page without a full reload.

💡 Space Analogy:

When you enter the bridge, you are immediately given the main navigation map, printed at Mission Control (SSR). It's already in your hands; no need to wait. But then you activate "live mode" on your tablet (CSR), and it starts receiving real-time updates from satellites, redrawing objects on your map.


2. Step 1: Preparing the Page

We will work with our planet list page resources/views/planets/index.blade.php. It already knows how to display data passed from the controller. Now we will add controls to it that will work via JS.

Add an "Update List" button and a container for notifications:

    <div class="controls">
        <h2>List of all known planets</h2>
        <button id="refresh-btn">Update via API</button>
    </div>
    <div id="notification-area" class="notification"></div>
    <hr>
    {{-- This div will be our container for dynamic updates --}}
    <div id="planet-list-container" class="planet-list">
        {{-- Include the "child" view that renders the initial list --}}
        @include('planets.partials.list', ['planets' => $planets])
    </div>

Note the @include('planets.partials.list', ...). We have moved the list display logic into a separate, reusable file.


Step 2: Creating a Reusable "Partial" View

Extracting repetitive parts into separate files is good practice.

Create the file resources/views/planets/partials/list.blade.php:

@forelse($planets as $planet)
    <div class="planet-card" id="planet-card-{{ $planet->id }}">
        <h3>{{ $planet->name }}</h3>
        <p>Solar System: {{ $planet->solar_system }}</p>
        <p>Diameter: {{ number_format($planet->size_km, 0, '.', ' ') }} km</p>
        <a href="{{ route('planets.show', $planet) }}">Learn more &rarr;</a>
        <button class="delete-btn" data-id="{{ $planet->id }}" data-url="{{ route('api.planets.destroy', $planet) }}">
            Decommission
        </button>
    </div>
@empty
    <p>There are no planets in the database.</p>
@endforelse
  • Important: Note that the URL for the delete button is now generated for the API route: route('api.planets.destroy', $planet). To do this, make sure you have a named resource in routes/api.php: Route::apiResource('planets', ...)->name('api.planets');

Step 3: Writing JavaScript for Dynamic Updates

Now for the interesting part. Let's create a JavaScript that will request a fresh list of planets from the API at the click of a button and redraw it.

Create the file public/js/planet-manager.js and include it in layouts/app.blade.php.

document.addEventListener('DOMContentLoaded', () => {
    const refreshBtn = document.getElementById('refresh-btn');
    const planetListContainer = document.getElementById('planet-list-container');
    const notificationArea = document.getElementById('notification-area');

    // Function to show notifications
    function showNotification(message, isError = false) {
        notificationArea.textContent = message;
        notificationArea.className = isError ? 'notification error' : 'notification success';
        setTimeout(() => {
            notificationArea.textContent = '';
            notificationArea.className = 'notification';
        }, 3000);
    }

    // Function to render a single planet card HTML
    function createPlanetCardHtml(planet) {
        // IMPORTANT: We generate the same HTML as in our partial view
        return `
            <div class="planet-card" id="planet-card-${planet.id}">
                <h3>${planet.name}</h3>
                <p>Solar System: ${planet.solar_system}</p>
                <p>Diameter: ${new Intl.NumberFormat().format(planet.size_km)} km</p>
                <a href="/planets/${planet.id}">Learn more &rarr;</a>
                <button class="delete-btn" data-id="${planet.id}" data-url="/api/planets/${planet.id}">
                    Decommission (JS)
                </button>
            </div>
        `;
    }

    // Function to fetch and redraw the list of planets
    async function fetchAndRenderPlanets() {
        showNotification('Requesting fresh data from orbital satellites...');
        try {
            const response = await fetch('/api/planets', {
                headers: { 'Accept': 'application/json' }
            });

            if (!response.ok) {
                throw new Error('Network error when receiving data.');
            }

            const planets = await response.json(); // Laravel will return { data: [...] } by default for a paginated resource

            planetListContainer.innerHTML = ''; // Clear the old list

            if (planets.data.length === 0) {
                planetListContainer.innerHTML = '<p>There are no planets in the database.</p>';
            } else {
                planets.data.forEach(planet => {
                    const cardHtml = createPlanetCardHtml(planet);
                    planetListContainer.innerHTML += cardHtml;
                });
            }
            showNotification('Data updated successfully!', false);
        } catch (error) {
            console.error('Error updating planet list:', error);
            showNotification(error.message, true);
        }
    }

    // Add an event listener to the button
    if (refreshBtn) {
        refreshBtn.addEventListener('click', fetchAndRenderPlanets);
    }

    // You can move the deletion logic from the previous chapter here,
    // so that all the JS is in one place.
});

3. Final Check

  1. Start the server (php artisan serve or make sure Herd is running).
  2. Recreate the database, if necessary: php artisan migrate:fresh --seed.
  3. Open the /planets page in your browser.
    • You should immediately see a list of planets generated by the server.
  4. Click the "Update via API" button.
    • You will see a loading notification.
    • The list should disappear for a moment and reappear, but this time it will be generated by JavaScript based on data received from the API.

You have successfully implemented a hybrid model!


Reinforcement Quiz

1. What is a hybrid rendering approach (SSR + CSR)?

2. What is the main advantage of initial server-side rendering (SSR)?

3. Why is `@include('planets.partials.list')` used in the example?

4. In the JavaScript code, we duplicate the HTML structure of the card. What could be a more advanced way to avoid this?

5. Why is it important that the API (`/api/planets`) and the JavaScript code (`createPlanetCardHtml`) generate consistent data/HTML?


🚀 Congratulations on completing Chapter 5!

You have come a long way from the basics of Blade to creating interactive hybrid pages. You have learned to:

  • Create and use Blade templates and layouts.
  • Organize web routes and controllers for CRUD operations.
  • Protect web forms and AJAX requests with CSRF tokens.
  • Integrate JavaScript for dynamic interaction with the API without reloading the page.

Your Mission Control Center is fully functional, secure, and interactive. You are ready for the next big stage — comparing this approach with other frameworks and learning best practices for production.