Привет, Хабр. На связи Артем, 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, то увидите список продуктов, загруженных с сервера. Вы можете создавать, редактировать и удалять продукты, и всё это будет происходить без перезагрузки страницы.
Теперь у нас есть основа, которую можно дополнять авторизацией, загрузкой изображений, фильтрацией данных и другими возможностями.
