Data fetching from database using laravel and vuejs

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Mastering Data Fetching: Seamless Integration of Laravel and Vue.js

As a senior developer, I frequently encounter scenarios where the data flow between a powerful backend framework like Laravel and an interactive frontend library like Vue.js seems broken. The common frustration—receiving a response in the network tab but seeing "none" or incorrect rendering on the screen—usually points to a mismatch in how the data is being serialized, routed, or bound between the two layers.

This post will dissect your provided setup, diagnose the likely cause of the issue, and demonstrate the correct, modern approach for fetching data from a Laravel API and displaying it dynamically using Vue.js. We will shift from complex server-side rendering attempts to the streamlined API-first methodology.

The Pitfall: Mixing Concerns in Blade

The code snippets you provided indicate an attempt to mix traditional server-side rendering (Blade) with client-side data fetching (Vue). The problem lies in how you are trying to inject PHP logic directly into your Vue loop:

// Inside data.blade.php
<tbody >
    <?php
    $i=1;
    ?>
    <tr v-for="message in messages">
        <td >{{ $i }}</td>
        <td > @{{ message.name }} </td> // <-- Problematic syntax for Vue binding
        <td > @{{ message.reference_id }} </td>
        ...
    </tr>
    <?php $i++; ?&gt; ?>
</tbody>

When you try to embed raw PHP logic and object access directly into a v-for loop, Blade is rendering the HTML before Vue has a chance to hydrate or process the data correctly. This often leads to either corrupted output or the client-side JavaScript failing to find the expected data structure, resulting in an empty display ("none").

The Solution: Adopting the API-First Approach

The most robust way to communicate between Laravel and Vue is by treating Laravel strictly as a RESTful API provider. The frontend (Vue.js) should be solely responsible for fetching data via HTTP requests (like Axios), and the backend should only return raw JSON.

Here is the corrected, streamlined architecture:

Step 1: Refine the Laravel Backend (API Endpoints)

Your controller and route are already set up perfectly for this goal. The key is ensuring the data returned is clean JSON.

Route (routes/web.php):

Route::get('/customers/list', [
    'as' => 'customerListData', 
    'uses' => 'CustomerController@showCustomersData'
]);

Controller (app/Http/Controllers/CustomerController.php):
The controller must return a JSON response, which is the standard for API interactions in Laravel.

use App\Models\Customer;
use Illuminate\Http\Request;

class CustomerController extends Controller
{
    public function showCustomersData()
    {
        $listCustomers = Customer::select('id', 'name', 'reference_id')
            ->orderBy('id', 'desc')
            ->get();
        
        // Return data as a clean JSON response
        return response()->json(['messages' => $listCustomers]);
    }
}

Step 2: Refine the Vue.js Frontend (Data Fetching)

The Vue component should now handle all HTTP communication, making the flow independent of Blade rendering logic. We use Axios to fetch the JSON data and bind it directly to the component state.

Vue Component (customers.js):
We remove the reliance on $set inside a callback function and rely on standard asynchronous fetching patterns.

new Vue({
    el: '#app',

    data: {
        messages: [] // Initialize an empty array to hold fetched data
    },

    mounted() {
        this.fetchMessages();
    },

    methods: {
        async fetchMessages() {
            try {
                // Fetch the JSON data from the Laravel API endpoint
                const response = await this.$http.get('/customers/list'); 
                
                // The response.data contains the JSON object returned by Laravel
                this.messages = response.data.messages; 
                console.log('Data fetched successfully:', this.messages);

            } catch (error) {
                console.error('Error fetching customer data:', error);
                // Handle errors gracefully for the user
                alert('Failed to load data. Check console for details.');
            }
        }
    }
});

Step 3: Refine the Blade View (Displaying Data)

The Blade file now only needs to serve as a container, using Vue directives (v-for) to dynamically render the data that Vue populates. We remove all PHP loop logic from this view entirely.

Blade View (data.blade.php):

@extends('layouts.app')

@section('title', 'Customers List')

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-10 col-md-offset-1">
                <div class="panel panel-default">
                    <div class="panel-heading">Customers List</div>
                    <div class="panel-body">
                        <table class="table table-hover table-bordered" id="app">
                            <thead>
                                <tr>
                                    <th>#</th>
                                    <th>Name</th>
                                    <th>Reference ID</th>
                                    <th>Action</th>
                                </tr>
                            </thead>
                            <tbody>
                                {{-- Vue will populate this table dynamically --}}
                                <tr v-for="message in messages" :key="message.id">
                                    <td>{{ $loop.index + 1 }}</td>
                                    <td>{{ message.name }}</td>
                                    <td>{{ message.reference_id }}</td>
                                    <td><a href="/customers/list/process/" class="btn btn-primary btn-xs" role="button">Click Here</a></td>
                                </tr>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

@section('scripts')
    <script src="/js/vue.js"></script>
    <script src="/js/vue-resource.min.js"></script>
    <script src="/js/customers.js"></script>
@endsection

Conclusion

By adhering to the principle of separation of concerns—where Laravel handles data persistence and delivery (API), and Vue.js handles presentation and asynchronous interaction—you eliminate the ambiguity that causes data fetching failures. Always aim for a clean JSON contract between your backend and frontend. As you build larger applications, adopting this decoupled approach is essential for maintaining scalability and ease of debugging. For deeper insights into building robust APIs within a Laravel application, I highly recommend exploring resources from laravelcompany.com.