Ваш компонент Vue отображает пустой список, даже если в базе данных Laravel есть записи. Эту проблему можно решить, создав JSON-эндпоинт в Laravel, запросив его ответ через Vue и выведя результаты с помощью  v-for .

Если вы ищете информацию о том,  как получить данные из базы данных с помощью vue (how to retrieve data from database using vue), правильный подход заключается в том, чтобы позволить Laravel обрабатывать подключение к базе данных и отправлять результаты во Vue в формате JSON. Vue использует  fetch()  или Axios для запроса этих данных, сохраняет их в реактивную переменную и автоматически обновляет страницу.

Мы создадим рабочий список товаров с использованием Laravel, MySQL, Vue 3 и Vite. Итоговая страница будет загружать названия и цены товаров напрямую из базы данных, с коррекционной обработкой загрузки и ошибок.

Как получить данные из базы данных с помощью Vue в Laravel: 6 шагов 

Прежде чем писать код, разберитесь, как взаимодействуют Laravel и Vue.

Vue работает в браузере. Он не может напрямую выполнять Eloquent-запросы Laravel или безопасно обращаться к вашей базе данных MySQL.

Laravel работает на сервере. Он читает записи из базы данных и возвращает информацию, необходимую фронтенду.

Запрос проходит следующий путь:

 User opens /products
        |
        v
Laravel returns Blade page
        |
        v
Vue component loads
        |
        v
fetch('/api/products')
        |
        v
Laravel ProductController
        |
        v
Product::get()
        |
        v
MySQL products table
        |
        v
JSON response
        |
        v
Vue displays products
 

Это разделение очень важно.

Вам не нужно помещать SQL-запросы внутрь компонентов Vue, и вам не нужно перезагружать всю страницу каждый раз, когда отображаемые данные меняются.

В этом примере мы будем использовать существующее приложение Laravel с установленным через npm Vue 3. Если Vue еще не установлен, инструкция по настройке приведена ниже.

Шаг 1. Настройка подключения к базе данных MySQL в Laravel 

Откройте файл  .env  вашего проекта Laravel.

Найдите настройки базы данных и обновите их:

 DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=vue_demo
DB_USERNAME=root
DB_PASSWORD=
 

Эти значения предполагают использование локальной среды разработки MySQL.

Измените имя пользователя и пароль в соответствии с вашей конфигурацией MySQL. Для продакшн-приложений используйте выделенную учетную запись базы данных с соответствующими правами вместо root.

База данных должна существовать до того, как Laravel сможет создавать в ней таблицы.

Откройте phpMyAdmin или сеанс командной строки MySQL и выполните:

 CREATE DATABASE IF NOT EXISTS vue_demo;
 

Теперь очистите кэш конфигурации Laravel:

 php artisan config:clear
 

Если вы используете XAMPP, убедитесь, что MySQL запущен в панели управления XAMPP.

Вы можете убедиться, что MySQL принимает соединения, выполнив:

 mysql -h 127.0.0.1 -P 3306 -u root -p
 

Введите настроенный пароль MySQL при появлении запроса.

Если в вашей локальной среде у учетной записи нет пароля, используйте соответствующие параметры входа.

После подключения выполните:

 SHOW DATABASES;
 

В результатах вы должны увидеть  vue_demo .

Если Laravel сообщает:

 SQLSTATE[HY000] [2002] Connection refused
 

Проверьте, запущен ли MySQL и совпадает ли порт с вашей конфигурацией  .env .

Если вы видите:

 SQLSTATE[HY000] [1045] Access denied for user
 

Проверьте свои учетные данные и права доступа к базе данных.

Более подробное объяснение локальных и удаленных подключений MySQL читайте в нашем руководстве по параметру хоста в соединениях MySQL.

 Чего ожидать: теперь в Laravel есть настройки подключения, необходимые для доступа к базе данных  vue_demo .

Шаг 2. Создание модели Product и таблицы products 

Мы будем использовать небольшую таблицу товаров, чтобы было легко увидеть, откуда именно берутся данные.

Выполните эту команду в директории вашего проекта Laravel:

 php artisan make:model Product -m
 

Laravel создает модель:

 app/Models/Product.php
 

И миграцию:

 database/migrations/xxxx_xx_xx_xxxxxx_create_products_table.php
 

Временная метка в имени файла миграции в вашем проекте будет другой.

Откройте файл миграции и замените его содержимое на:

 <?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->decimal('price', 10, 2);
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};
 

Это создает таблицу со следующими столбцами:

 id
name
price
created_at
updated_at
 

Столбец  id  является первичным ключом.

В столбце  price  используется тип  decimal(10,2) , что подходит для хранения цен с фиксированной точностью в этом примере.

Затем откройте:

 app/Models/Product.php
 

Обновите его:

 <?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    protected $fillable = [
        'name',
        'price',
    ];
}
 

Свойство  $fillable  позволяет массово заполнять эти поля при использовании метода Eloquent  create() .

Теперь выполните:

 php artisan migrate
 

Laravel должен сообщить, что миграция products выполнена успешно.

Если у вас уже есть таблица  products , не запускайте дублирующую миграцию создания таблицы. Используйте существующую структуру таблицы или создайте миграцию, которая изменяет только необходимые вам столбцы.

Вставка тестовых записей 

Откройте Laravel Tinker:

 php artisan tinker
 

Вставьте несколько товаров:

 \App\Models\Product::create([
    'name' => 'Laptop',
    'price' => 2500.00,
]);

\App\Models\Product::create([
    'name' => 'Keyboard',
    'price' => 150.00,
]);

\App\Models\Product::create([
    'name' => 'Mouse',
    'price' => 75.00,
]);

\App\Models\Product::create([
    'name' => 'Monitor',
    'price' => 850.00,
]);
 

После каждой успешной вставки вы должны увидеть вывод модели Eloquent.

Выйдите из Tinker:

 exit
 

Проверьте записи в MySQL:

 SELECT id, name, price
FROM products
ORDER BY id;
 

Для новой пустой таблицы результат должен выглядеть так:

 +----+----------+---------+
| id | name     | price   |
+----+----------+---------+
|  1 | Laptop   | 2500.00 |
|  2 | Keyboard |  150.00 |
|  3 | Mouse    |   75.00 |
|  4 | Monitor  |  850.00 |
+----+----------+---------+
 

Если ваша таблица уже содержала данные, идентификаторы (ID) могут отличаться.

 Чего ожидать: четыре записи о товарах теперь сохранены в MySQL и могут быть извлечены через Laravel.

Шаг 3. Создание контроллера Laravel для выборки записей MySQL 

Для Vue требуется HTTP-эндпоинт, который возвращает данные о товарах.

Мы создадим этот эндпоинт с помощью контроллера Laravel.

Выполните:

 php artisan make:controller ProductController
 

Откройте:

 app/Http/Controllers/ProductController.php
 

Замените содержимое на:

 <?php

namespace App\Http\Controllers;

use App\Models\Product;
use Illuminate\Http\JsonResponse;

class ProductController extends Controller
{
    public function index(): JsonResponse
    {
        $products = Product::query()
            ->select('id', 'name', 'price')
            ->orderBy('id', 'asc')
            ->get();

        return response()->json($products);
    }
}
 

Здесь Laravel извлекает данные из MySQL.

Давайте изучим запрос.

 Product::query()
 

Создает Eloquent-запрос для таблицы  products .

 ->select('id', 'name', 'price')
 

Выбирает только те поля, которые необходимы фронтенду.

 ->orderBy('id', 'asc')
 

Сортирует записи по ID.

 ->get()
 

Выполняет запрос и извлекает соответствующие товары.

И наконец:

 return response()->json($products);
 

Преобразует результат в JSON HTTP-ответ.

Для приложений такого типа возврат JSON лучше, чем отправка сгенерированного HTML из контроллера. Vue может сам решать, как отображать записи, без изменения ответа бэкенда.

Также нет необходимости возвращать из базы данных каждый столбец.

Например, если ваша таблица товаров в дальнейшем будет содержать внутренние издержки или информацию о поставщиках, эти поля не должны автоматически становиться видимыми для каждого пользователя фронтенда.

При работе с более сложными условиями наше руководство по выбору столбцов SQL на основе условий охватывает сторону запросов к базе данных.

 Чего ожидать: ваш контроллер может извлекать записи о товарах и возвращать только выбранные поля.

Шаг 4. Создание роута Laravel, который может вызывать Vue 

Теперь нам нужен URL-адрес для контроллера.

Откройте:

 routes/web.php
 

Добавьте:

 use App\Http\Controllers\ProductController;
use Illuminate\Support\Facades\Route;

Route::get('/api/products', [ProductController::class, 'index']);

Route::view('/products', 'products');
 

Первый роут возвращает JSON:

 GET /api/products
 

Второй роут отображает страницу, которая будет содержать наш компонент Vue:

 GET /products
 

Зачем помещать эти роуты внутрь  web.php ?

Для этого руководства Vue и Laravel используют один и тот же источник (origin). JSON GET-роута внутри  web.php  достаточно, и он работает без установки дополнительной настройки маршрутизации API в Laravel.

Если вы создаете отдельный REST API, вместо этого вы можете использовать  routes/api.php .

В инсталляциях Laravel, где маршрутизация API не была настроена, вы можете включить ее с помощью:

 php artisan install:api
 

Эта команда также может установить зависимости, связанные с аутентификацией API, и сгенерировать дополнительные файлы. Для этого примера она не требуется.

Если вы определяете роут в  routes/api.php , помните, что Laravel обычно автоматически применяет префикс  /api .

Например:

 Route::get('/products', [ProductController::class, 'index']);
 

Превратится в:

 /api/products
 

Не определяйте случайно  /api/products  внутри  api.php , чтобы в итоге не запрашивать  /api/api/products .

Тестирование JSON-эндпоинта перед написанием кода Vue 

Запустите Laravel:

 php artisan serve
 

Откройте этот URL в браузере:

 http://127.0.0.1:8000/api/products
 

Ответ должен выглядеть примерно так:

 [
    {
        "id": 1,
        "name": "Laptop",
        "price": "2500.00"
    },
    {
        "id": 2,
        "name": "Keyboard",
        "price": "150.00"
    },
    {
        "id": 3,
        "name": "Mouse",
        "price": "75.00"
    },
    {
        "id": 4,
        "name": "Monitor",
        "price": "850.00"
    }
]
 

Обратите внимание, что  price  может возвращаться в виде строки.

Это нормально, когда десятичные значения MySQL сериализуются через Laravel.

Если эндпоинт не возвращает записи, остановитесь здесь и исследуйте ответ Laravel.

 Не начинайте менять код Vue для исправления сломанного контроллера или подключения к базе данных.

Шаг 5. Установка Vue 3 и подключение его к Laravel Vite 

Laravel использует Vite для сборки фронтенд-ассетов.

Мы установим Vue 3 и его плагин для Vite.

Из директории проекта Laravel выполните:

 npm install
 

Затем:

 npm install vue
 

Установите плагин Vue:

 npm install --save-dev @vitejs/plugin-vue
 

Теперь откройте:

 vite.config.js
 

Для стандартного проекта Laravel с точками входа JavaScript и CSS используйте:

 import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/css/app.css',
                'resources/js/app.js',
            ],
            refresh: true,
        }),
        vue(),
    ],
});
 

Если в проекте уже есть пользовательские плагины Vite или пути к ассетам, сохраните их и добавьте поддержку Vue в существующую конфигурацию.

Затем откройте:

 resources/js/app.js
 

Замените или отредактируйте его следующим образом:

 import './bootstrap';

import { createApp } from 'vue';
import ProductsList from './components/ProductsList.vue';

createApp(ProductsList).mount('#app');
 

Функция  createApp()  инициализирует Vue.

Инструкция  mount('#app')  указывает Vue, где отображать компонент.

Позже наша страница Blade будет содержать:

 <div id="app"></div>
 

Vue смонтируется внутри этого элемента.

Это распространенное место, где разработчики путаются.

Laravel Blade и Vue могут работать вместе, но они выполняют разные задачи.

Blade генерирует начальную HTML-страницу на сервере. Vue работает в браузере после загрузки JavaScript.

Вам не нужно передавать каждую запись базы данных в переменные Blade, когда Vue уже запрашивает JSON-эндпоинт.

Для проектов, в которых смешиваются значения, отрендеренные на сервере, и JavaScript-компоненты, см. статью о том, как задавать переменные в шаблонах Laravel Blade.

 Чего ожидать: теперь Laravel Vite настроен на компиляцию и загрузку однофайловых компонентов Vue.

Шаг 6. Получение и отображение записей базы данных Laravel во Vue 

Создайте эту папку, если она не существует:

 resources/js/components
 

Внутри этой папки создайте:

 ProductsList.vue
 

Добавьте следующий код:

 <script setup>
import { ref, onMounted } from 'vue';

const products = ref([]);
const loading = ref(false);
const error = ref('');

async function loadProducts() {
    loading.value = true;
    error.value = '';

    try {
        const response = await fetch('/api/products', {
            method: 'GET',
            headers: {
                Accept: 'application/json',
            },
        });

        if (!response.ok) {
            throw new Error(
                `Failed to load products: HTTP ${response.status}`
            );
        }

        const data = await response.json();

        if (!Array.isArray(data)) {
            throw new Error('Unexpected API response format');
        }

        products.value = data;
    } catch (err) {
        error.value = err.message;
    } finally {
        loading.value = false;
    }
}

onMounted(() => {
    loadProducts();
});
</script>

<template>
    <main class="products-page">
        <h1>Products</h1>

        <button
            type="button"
            @click="loadProducts"
            :disabled="loading"
        >
            {{ loading ? 'Loading...' : 'Refresh products' }}
        </button>

        <p v-if="loading">
            Loading products...
        </p>

        <p v-else-if="error">
            {{ error }}
        </p>

        <p v-else-if="products.length === 0">
            No products found.
        </p>

        <table v-else>
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Product name</th>
                    <th>Price</th>
                </tr>
            </thead>

            <tbody>
                <tr
                    v-for="product in products"
                    :key="product.id"
                >
                    <td>{{ product.id }}</td>
                    <td>{{ product.name }}</td>
                    <td>
                        {{ Number(product.price).toFixed(2) }}
                    </td>
                </tr>
            </tbody>
        </table>
    </main>
</template>

<style scoped>
.products-page {
    max-width: 900px;
    margin: 40px auto;
    font-family: Arial, sans-serif;
}

table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 20px;
}

th,
td {
    border: 1px solid #ddd;
    padding: 12px;
    text-align: left;
}

th {
    background: #f5f5f5;
}

button {
    padding: 10px 16px;
    cursor: pointer;
}
</style>
 

Этот компонент делает гораздо больше, чем просто извлекает записи.

Он обрабатывает первоначальный запрос, показывает состояние загрузки, определяет неуспешные HTTP-ответы, проверяет формат возвращаемых данных и выводит ошибку в случае сбоя запроса.

Он также включает кнопку обновления, позволяющую перезагружать товары без обновления всей страницы Laravel.

Почему важен ref() 

Посмотрите на:

 const products = ref([]);
 

Функция  ref()  во Vue создает реактивное состояние.

Когда выполняется эта строка:

 products.value = data;
 

Vue обнаруживает изменение и обновляет таблицу.

Вам не нужно вручную пересобирать HTML или вызывать функцию обновления DOM.

Почему используется onMounted() 

Этот код:

 onMounted(() => {
    loadProducts();
});
 

Запускается после монтирования компонента.

Это естественное место для загрузки исходных данных для клиентского списка товаров.

Избегайте размещения одного и того же запроса на выборку в нескольких хуках жизненного цикла, если вам специально не требуется несколько запросов.

В противном случае вы можете случайно загрузить данные дважды.

Почему следует проверять response.ok 

Fetch API не отклоняет промис автоматически, когда сервер возвращает ошибку HTTP, такую как 404 или 500.

Именно поэтому мы проверяем:

 if (!response.ok) {
    throw new Error(
        `Failed to load products: HTTP ${response.status}`
    );
}
 

Без этой проверки ваш код может попытаться отобразить ответ с ошибкой так, будто он содержит товары.

Почему для v-for нужен уникальный ключ (key) 

Таблица содержит:

 <tr
    v-for="product in products"
    :key="product.id"
>
 

Директива  v-for  повторяет строку для каждого товара.

Ключ помогает Vue определить, какая запись принадлежит каждой отрендеренной строке при изменении списка.

Используйте стабильный ID базы данных вместо индекса массива, когда ID доступен.

Создание страницы Laravel Blade 

Теперь создайте:

 resources/views/products.blade.php
 

Добавьте:

 <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">

    <meta
        name="viewport"
        content="width=device-width, initial-scale=1"
    >

    <title>Laravel Vue Products</title>

    @vite([
        'resources/css/app.css',
        'resources/js/app.js'
    ])
</head>

<body>

    <div id="app"></div>

</body>
</html>
 

Файлу Blade не нужно делать запросы к базе данных.

Он просто предоставляет HTML-документ, ассеты Vite и элемент монтирования.

Vue заботится о запросе и отображении товаров.

Запустите сервер разработки фронтенда:

 npm run dev
 

Держите сервер разработки Laravel запущенным в другом терминале:

 php artisan serve
 

Теперь перейдите по адресу:

 http://127.0.0.1:8000/products
 

Вы должны увидеть таблицу, содержащую записи о товарах из MySQL.

Проверка того, что Vue извлекает реальные данные из MySQL 

Откройте инструменты разработчика в браузере и перейдите на вкладку Network (Сеть).

Обновите страницу товаров.

Найдите:

 GET /api/products
 

Кликните по запросу и проверьте его статус.

Успешный ответ должен показывать:

 Status: 200 OK
Content-Type: application/json
 

Вкладка Response или Preview должна содержать записи о товарах.

На вашей странице должно отображаться что-то вроде этого:

 Products

[Refresh products]

ID   Product name    Price
1    Laptop          2500.00
2    Keyboard         150.00
3    Mouse             75.00
4    Monitor          850.00
 

Чтобы убедиться, что Vue отображает актуальный контент базы данных, а не жестко закодированные значения, откройте Tinker:

 php artisan tinker
 

Вставьте еще одну запись:

 \App\Models\Product::create([
    'name' => 'USB Cable',
    'price' => 25.00,
]);
 

Выйдите из Tinker и нажмите кнопку  Refresh products.

Должен появиться новый товар.

Никаких изменений в исходный код Vue вносить не требуется.

Это подтверждает полное соединение от MySQL через Laravel до браузера.

Исправление проблемы, когда Vue показывает пустой список при наличии записей в Laravel 

Если таблица по-прежнему ничего не отображает, диагностируйте проблему от бэкенда к фронтенду.

Этот порядок быстрее, чем изменение несвязанных настроек Vue.

API Laravel возвращает 404 Not Found 

Сначала проверьте свой роут:

 php artisan route:list --path=api/products
 

Вы должны увидеть GET-роут, указывающий на  ProductController .

Если его нет, проверьте  routes/web.php .

Вы также можете очистить кэш роутов:

 php artisan route:clear
 

Убедитесь, что фронтенд запрашивает правильный путь:

 fetch('/api/products')
 

Небольшого несоответствия между  /api/product  и  /api/products  достаточно для получения ошибки 404.

Vue возвращает ошибку Unexpected token '<', "<!DOCTYPE" is not valid JSON 

Эта ошибка означает, что в ответ пришел HTML, а не тот JSON, который ожидал  response.json() .

Распространенные причины включают неверный маршрут, страницу ошибки Laravel или перенаправление на экран входа в систему.

Откройте вкладку Network в браузере и изучите тело ответа.

Если оно содержит HTML-документ, выясните, почему сервер вернул именно его.

Не пытайтесь решить проблему, просто заменяя каждый вызов  response.json()  на  response.text() .

Это изменит способ парсинга ответа, но не решит основную проблему с API.

API возвращает пустой массив 

Предположим,  /api/products  возвращает:

 []
 

Это означает, что Laravel успешно вернул пустую коллекцию.

Проверьте записи напрямую:

 php artisan tinker
 

Запустите:

 \App\Models\Product::count();
 

Затем:

 \App\Models\Product::all();
 

Если количество равно нулю, ваша настроенная база данных не содержит ожидаемых записей.

Проверьте  DB_DATABASE , активное окружение и любые условия запроса.

Частая ошибка — добавление записей в одну базу данных через phpMyAdmin, в то время как Laravel подключен к другой.

Laravel возвращает ошибку сервера 500 

Ответ 500 означает, что при обработке запроса на сервере произошла ошибка.

Изучите логи приложения Laravel по пути:

 storage/logs/
 

Фактическое имя файла зависит от настроенного канала логирования.

Ищите исключения SQL, отсутствующие таблицы, неверные имена моделей или ошибки PHP.

Например:

 SQLSTATE[42S02]: Base table or view not found
 

Обычно это означает, что ожидаемая таблица не существует в подключенной базе данных.

Запустите:

 php artisan migrate:status
 

Проверьте, была ли применена миграция продуктов.

Не думайте, что любая ошибка 500 вызвана Vue. Браузер лишь сообщает, что Laravel не смог завершить запрос.

Vue ничего не отображает и запрос не отправляется 

Если на вкладке Network не отображается  /api/products , возможно, компонент Vue не монтируется.

Проверьте импорт  app.js :

 import ProductsList from './components/ProductsList.vue';
 

Проверьте инструкцию монтирования:

 createApp(ProductsList).mount('#app');
 

Затем убедитесь, что Blade-шаблон содержит:

 <div id="app"></div>
 

Также проверьте вкладку Console в браузере на наличие ошибок сборки Vite или загрузки модулей.

Если вы запускаете проект локально, убедитесь, что Vite запущен:

 npm run dev
 

Для продакшн-сборки:

 npm run build
 

Разверните сгенерированные фронтенд-ассеты вместе с приложением Laravel.

Fetch работает локально, но падает из-за CORS 

Ошибки междоменных запросов (CORS) возникают, когда браузер запрашивает другой источник без необходимых разрешений.

Например:

 Vue frontend:
http://localhost:5173

Laravel backend:
http://127.0.0.1:8000
 

Это разные источники (origin).

Если вы хостите Vue отдельно, настройте параметры CORS в Laravel для нужного источника фронтенда и используйте правильный URL API.

Для запросов из браузера с учетными данными (credentials) серверу также необходимы соответствующие настройки учетных данных и источников.

Не отключайте безопасность браузера, чтобы запрос заработал.

Настройка в этом туаториале позволяет избежать проблемы за счет обслуживания страницы Vue и эндпоинта API из одного источника Laravel.

Список продуктов не обновляется после добавления записей 

Компонент загружает продукты при монтировании.

Он не может автоматически узнать, что другой пользователь добавил еще один продукт в MySQL.

Это ожидаемое поведение.

Кнопка обновления снова вызывает  loadProducts()  и получает последние записи.

Если позже вы добавите формы создания или редактирования продуктов, вызывайте  loadProducts()  после успешного сохранения.

Для приложений, которым требуются постоянные обновления, рассмотрите поллинг (опрос) или подход с бродкастингом в реальном времени. Ни то, ни другое не нужно для базового списка продуктов.

Используйте пагинацию, когда таблица продуктов становится большой 

Исходный контроллер использует:

 ->get();
 

Это извлекает все подходящие записи.

Это нормально для небольшого примера, но выборка каждой строки может стать ресурсоемкой по мере роста таблицы.

Laravel предоставляет пагинацию через Eloquent.

Обновите метод контроллера:

 public function index(): JsonResponse
{
    $products = Product::query()
        ->select('id', 'name', 'price')
        ->orderBy('id')
        ->paginate(10);

    return response()->json($products);
}
 

Теперь эндпоинт возвращает ответ с пагинацией, а не простой массив.

JSON содержит записи продуктов в поле  data  вместе с метаданными пагинации.

Вам нужно изменить присваивание во Vue с:

 products.value = data;
 

На:

 products.value = data.data;
 

Поскольку структура ответа изменилась.

Также удалите или скорректируйте предыдущую проверку  Array.isArray(data) , так как теперь ответом верхнего уровня является объект.

Например, соответствующий код парсинга будет выглядеть так:

 const result = await response.json();

if (!Array.isArray(result.data)) {
    throw new Error('Unexpected pagination response');
}

products.value = result.data;
 

Laravel также предоставляет метаданные, такие как текущая и последняя страницы, которые можно использовать для создания элементов управления пагинацией.

Следующую страницу можно запросить с помощью:

 fetch('/api/products?page=2')
 

Я бы использовал встроенную пагинацию Laravel вместо написания ручной обработки SQL  LIMIT  и  OFFSET  для обычного списка продуктов.

Для существующего проекта с кастомной маршрутизацией или проблемами с базой данных вы можете обратиться за практической помощью в разработке к специалистам HeadsNTail.

Часто задаваемые вопросы 

Может ли Vue подключаться напрямую к базе данных MySQL? 

Нет. Работающее в браузере Vue-приложение не должно открывать прямое соединение с MySQL.

Учетные данные базы данных и запросы на стороне сервера должны находиться в Laravel. Vue должен взаимодействовать с Laravel через HTTP-эндпоинт, который возвращает только те данные, к которым у пользователя есть права доступа.

Как получать данные из API Laravel с помощью Axios во Vue 3? 

Установите Axios:

 npm install axios
 

Затем используйте его внутри компонента Vue:

 import axios from 'axios';
import { ref, onMounted } from 'vue';

const products = ref([]);

onMounted(async () => {
    try {
        const response = await axios.get('/api/products');
        products.value = response.data;
    } catch (error) {
        console.error('Failed to load products:', error);
    }
});
 

В обычных случаях Axios автоматически парсит JSON-ответы.

Существующий контроллер Laravel менять не нужно. Этот пример предполагает, что эндпоинт возвращает простой массив, а не ответ с пагинацией.

Как отобразить данные из базы данных Laravel в таблице Vue? 

Получите записи из эндпоинта Laravel и сохраните их в реактивный массив.

Затем используйте  v-for  для генерации строк таблицы:

 <tr v-for="product in products" :key="product.id">
    <td>{{ product.name }}</td>
    <td>{{ product.price }}</td>
</tr>
 

Vue будет обновлять таблицу каждый раз при изменении реактивного массива.

Как получить одну запись из базы данных во Vue с помощью Laravel? 

Создайте маршрут Laravel, который принимает ID продукта:

 Route::get('/api/products/{product}', function (
    \App\Models\Product $product
) {
    return response()->json(
        $product->only(['id', 'name', 'price'])
    );
});
 

Привязка моделей к маршрутам (Route Model Binding) в Laravel извлекает соответствующий продукт или возвращает ответ 404, если он не существует.

Получите запись из Vue:

 const response = await fetch('/api/products/1', {
    headers: {
        Accept: 'application/json',
    },
});

if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
}

const product = await response.json();
 

Для конфиденциальных записей добавьте соответствующие проверки аутентификации и авторизации перед их возвратом.

Почему Vue показывает пустой массив, когда в MySQL есть данные? 

Сначала откройте эндпоинт Laravel API напрямую и изучите JSON.

Если Laravel возвращает  [] , проверьте запрос Eloquent и базу данных, настроенную в  .env . Если Laravel возвращает записи, но Vue их не отображает, проверьте парсинг ответа в компоненте, реактивное состояние и консоль браузера.

В качестве следующего шага после изучения того,  как получать данные из базы данных с помощью Vue, добавьте поиск на стороне сервера и пагинацию, а затем защитите любые эндпоинты, которые раскрывают приватные данные.