Пошаговое руководство для начинающих по разработке SPA на Laravel и Vue.js

Привет, Хабр. На связи Артем, Laravel-разработчик в Webest, и я написал инструкцию для начинающих разработчиков по созданию полноценного локального приложения с бэкендом на Laravel и фронтендом на Vue.js. 

Одностраничные приложения (SPA) стали стандартом для создания динамичных интерфейсов. Laravel и Vue.js — популярный стек, который позволяет быстро создавать и масштабировать приложения.

Пошагово разберу, как настроить среду разработки, создать API на Laravel, реализовать динамический интерфейс с использованием Vue.js и связать эти две части в единое приложение. 

После прочтения статьи вы сможете развернуть свое собственное SPA и использовать его как основу для реализации своих идей по функционалу. Этот пример станет отличной отправной точкой для создания более сложных проектов.

Практический пример

Рассмотрим пример создания SPA, где Laravel выступает в роли бэкенда, а Vue.js — фронтенда. Мы создадим простое приложение для управления списком продуктов.

Используем следующие технологии:

  • PHP версии 8.3,

  • Composer версии 2.7.4,

  • Laravel версии 11.31,

  • Node.js версии 18.20.6,

  • Npm версии 10.8.2

  • Vue.js версии 3.5.13.

В качестве базы данных используем SQLite, которая по умолчанию входит в начальную установку Laravel 11. SQLite хранит данные в файле базы данных внутри проекта (в папке database). Однако, если вам удобнее использовать другую базу данных (например, MySQL, PostgreSQL или MariaDB), вы можете легко настроить её в файле .env. Laravel поддерживает множество популярных СУБД.

1. Настройка Laravel

Установка проекта.

Для начала создаем новый проект Laravel. Эта команда создаст свежую установку Laravel с последними зависимостями и структурами:

composer create-project laravel/laravel laravel-vue-spa

После завершения установки перейдём в папку проекта:

cd laravel-vue-spa  

Теперь, находясь в папке проекта, выполним следующие команды.

Генерация ключа приложения.

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

php artisan key:generate  

Создание модели, миграции и контроллера.

Далее создаём модель Product, миграцию и контроллер с помощью одной команды. Это позволит быстро создать все необходимые компоненты для работы с продуктами:

php artisan make:model Product -mc

Добавление полей в миграцию.

Откроем миграцию database/migrations/create_products_table и добавим необходимые поля для продуктов, такие как имя, описание и цена.

public function up()
{
    Schema::create('products', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->text('description')->nullable();
        $table->decimal('price', 8, 2);
        $table->timestamps();
    });
}

Теперь нужно выполнить миграцию, чтобы создать таблицу products в базе данных:

php artisan migrate

Добавление заполняемых полей в модель

Без указания fillable Laravel запретит массовое заполнение полей, что приведет к ошибке при создании или обновлении модели. Чтобы этого избежать, необходимо разрешить заполнение определённых полей.

Откройте файл app/Models/Product.php и добавьте:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

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

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

В контроллере app/Http/Controllers/ProductController создаём методы для работы с продуктами: получение всех продуктов, создание, получение данных одного продукта, обновление и удаление:

namespace App\Http\Controllers;

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

class ProductController extends Controller
{
    public function index()
    {
        return Product::all();
    }

    public function store(Request $request)
    {
        return Product::create($request->all());
    }

    public function show(Product $product)
    {
        return $product;
    }

    public function update(Request $request, Product $product)
    {
        $product->update($request->all());
        return $product;
    }

    public function destroy(Product $product)
    {
        $product->delete();
        return response()->noContent();
    }
}

Создание маршрутов API

В Laravel 11 по умолчанию отсутствует файл маршрутов api.php. Вместо этого его можно создать с помощью простой команды Artisan:

php artisan install:api

Добавим маршруты в routes/api.php:

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

Route::apiResource('products', ProductController::class);

Обработка маршрутов для SPA в web.php

При использовании одностраничных приложений с Vue.js, важно, чтобы Laravel корректно обрабатывал все пути, включая те, которые относятся к внутренним маршрутам Vue Router. Например, при обновлении страницы создания продукта /products/create, Laravel по умолчанию будет пытаться обработать этот запрос на сервере, что приведет к ошибке 404, так как такой маршрут не существует в файле маршрутов Laravel. Чтобы избежать этой проблемы, необходимо настроить один универсальный маршрут, который будет обрабатывать все запросы и отдавать только одну страницу, на которой будет загружаться весь интерфейс Vue.js.

В файле routes/web.php замените стандартный маршрут:

Route::get('/', function () {
    return view('welcome');
});

На следующий код:

Route::get('/{any}', function () {
    return view('welcome');
})->where('any', '.*');

2. Настройка Vue.js

Установим Vue.js, Vue Router и Axios через npm:

npm install vue@latest vue-router@latest axios

Редактирование welcome.blade.php

Файл welcome.blade.php – это шаблон, который Laravel использует по умолчанию для отображения главной страницы при запуске приложения. В стандартной установке Laravel он содержит статический контент, но для работы с Vue.js нам нужно заменить его на динамический шаблон, который будет служить точкой монтирования для Vue-приложения.

Редактируя этот файл, мы создаем базовую HTML-структуру, в которой подключаем Vue и Vite, а также определяем контейнер #app, в который будет загружаться наше приложение.

Обновленный resources/views/welcome.blade.php:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Vue Laravel App</title>

    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
    <div id="app"></div>

    <script type="module" src="{{ Vite::asset('resources/js/app.js') }}"></script>
</body>
</html>

Установка и настройка Vite для Vue

Vite — это современный инструмент для сборки фронтенда, который значительно ускоряет разработку благодаря горячей перезагрузке и быстрой компиляции. Для работы с Vue в Vite требуется специальный плагин. Установим его:

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

Vite требует явного подключения плагинов. Откроем файл vite.config.js и добавим конфигурацию для Laravel и Vue:

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(),
    ],
});

Настройка bootstrap.js

Файл resources/js/bootstrap.js предназначен для настройки глобальных зависимостей, таких как Axios и Vue.

Откроем его и добавим базовую конфигурацию:

import axios from 'axios';
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';

// Настройка Axios для отправки AJAX-запросов
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

// Инициализация Vue и подключение маршрутизации
const app = createApp(App);
app.use(router);
app.mount('#app');

Создание App.vue

В Vue.js компонент App.vue является корневым компонентом приложения, который служит точкой входа для всех остальных компонентов. В нем мы подключаем Vue Router и определяем, где будет отображаться содержимое страниц.

Создадим файл resources/js/App.vue и добавим в него следующий код:

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App',
};
</script>

Создание компонентов Vue

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

Создадим компонент для отображения списка продуктов resources/js/components/ProductList.vue:

<template>
  <div>
    <h1>Product List</h1>
    <ul>
      <li v-for="product in products" :key="product.id">
        <strong>{{ product.name }}</strong> - {{ product.price }}₽
        <router-link :to="`/products/${product.id}/edit`">Edit</router-link>
        <button @click="deleteProduct(product.id)" class="delete-btn">Delete</button>
      </li>
    </ul>
    <router-link to="/products/create" class="create-btn">Create New Product</router-link>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      products: [],
    };
  },
  async created() {
    const response = await axios.get('/api/products');
    this.products = response.data;
  },
  methods: {
    async deleteProduct(id) {
      if (confirm('Are you sure you want to delete this product?')) {
        await axios.delete(`/api/products/${id}`);
        this.products = this.products.filter(product => product.id !== id);
      }
    },
  },
};
</script>

<style scoped>
.create-btn, .delete-btn {
  margin-left: 10px;
  padding: 5px 10px;
  border: none;
  cursor: pointer;
}
.delete-btn {
  background-color: red;
  color: white;
}
.create-btn {
  display: block;
  margin-top: 20px;
  background-color: green;
  color: white;
  text-align: center;
  text-decoration: none;
  padding: 8px 12px;
}
</style>

Создадим компонент для редактирования продукта в resources/js/components/EditProduct.vue:

<template>
    <div>
      <h1>Edit Product</h1>
      <form @submit.prevent="updateProduct">
        <label>Product Name:</label>
        <input v-model="product.name" type="text" required />
  
        <label>Description:</label>
        <textarea v-model="product.description"></textarea>
  
        <label>Price (₽):</label>
        <input v-model="product.price" type="number" step="0.01" required />
                <button type="submit" class="save-btn">Save</button>
        <router-link to="/" class="cancel-btn">Cancel</router-link>
      </form>
    </div>
  </template>
  
  <script>
  import axios from 'axios';
  
  export default {
    data() {
      return {
        product: {},
      };
    },
    async created() {
      const response = await axios.get(`/api/products/${this.$route.params.id}`);
      this.product = response.data;
    },
    methods: {
      async updateProduct() {
        await axios.put(`/api/products/${this.product.id}`, this.product);
        this.$router.push('/');
      },
    },
  };
  </script>
  
  <style scoped>
  form {
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  input, textarea {
    width: 100%;
    padding: 8px;
  }
  button {
    margin-top: 10px;
    padding: 8px 12px;
    cursor: pointer;
  }
  .save-btn {
    background-color: blue;
    color: white;
  }
  .cancel-btn {
    background-color: gray;
    color: white;
    text-decoration: none;
    display: inline-block;
    padding: 8px 12px;
    text-align: center;
  }
  </style>

Создадим компонент для создания нового продукта в resources/js/components/CreateProduct.vue:

<template>
  <div>
    <h1>Create Product</h1>
    <form @submit.prevent="createProduct">
      <label>Product Name:</label>
      <input v-model="product.name" type="text" required />

      <label>Description:</label>
      <textarea v-model="product.description"></textarea>

      <label>Price (₽):</label>
      <input v-model="product.price" type="number" step="0.01" required />
      <button type="submit" class="create-btn">Create</button>
      <router-link to="/" class="cancel-btn">Cancel</router-link>
    </form>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      product: {
        name: '',
        description: '',
        price: 0,
      },
    };
  },
  methods: {
    async createProduct() {
      await axios.post('/api/products', this.product);
      this.$router.push('/');
    },
  },
};
</script>

<style scoped>
form {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
input, textarea {
  width: 100%;
  padding: 8px;
}
button {
  margin-top: 10px;
  padding: 8px 12px;
  cursor: pointer;
}
.create-btn {
  background-color: green;
  color: white;
}
.cancel-btn {
  background-color: gray;
  color: white;
  text-decoration: none;
  display: inline-block;
  padding: 8px 12px;
  text-align: center;
}
</style>

Настройка маршрутизации с Vue Router

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

Создаем файл resources/js/router/index.js и настраиваем маршруты:

import { createRouter, createWebHistory } from 'vue-router';
import ProductList from '../components/ProductList.vue';
import EditProduct from '../components/EditProduct.vue';
import CreateProduct from '../components/CreateProduct.vue';

const routes = [
  {
    path: '/',
    component: ProductList,
  },
  {
    path: '/products/create',
    component: CreateProduct,
  },
  {
    path: '/products/:id/edit',
    component: EditProduct,
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;

Подключение Vue Router к приложению

Далее импортируем и подключаем Vue Router в resources/js/app.js:

import { createApp } from 'vue';
import App from './App.vue';
import router from './router';

createApp(App).use(router).mount('#app');

3. Запуск приложения

Соберем фронтенд:

npm run build

Запустим сервер Laravel:

php artisan serve

Если вы откроете браузер по адресу http://localhost:8000, то увидите список продуктов, загруженных с сервера. Вы можете создавать, редактировать и удалять продукты, и всё это будет происходить без перезагрузки страницы.

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

Источник: https://habr.com/ru/articles/890170/

Опубликовано в категории: Статьи