Пошаговое руководство для начинающих по разработке 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *