SPA приложения с помощью Laravel и Vue
Благодаря чистому синтаксису и выразительности, а также многим другим качествам, Laravel является одним из самых популярных PHP-фреймворков, используемых разработчиками. До запуска пользовательского интерфейса Laravel одной из его ключевых функций была поддержка Vue.js по умолчанию от Laravel v5.3 до v6. Vue — это современная интерфейсная среда JavaScript, используемая для создания пользовательских интерфейсов.
В этой статье мы покажем, как создать одностраничное приложение с помощью Laravel и Vue.
Почему Laravel и Vue хороши вместе?
Вот некоторые из ключевых преимуществ использования Laravel и Vue для создания полноценного рабочего процесса для ваших проектов:
- Исходный код объединен в один проект, а не отдельные проекты для бэкэнда и фронтенда.
- Установка и настройка просты
- Одно развертывание может работать с обеими платформами вместе.
Что такое SPA? (одностраничное приложение)
Одностраничное приложение (сокращенно SPA) динамически загружает новые данные с веб-сервера на веб-страницу без необходимости обновления всей страницы.
Примеры популярных веб-сайтов, использующих SPA, включают gmail.com и youtube.com — другими словами, SPA широко распространены. Большинство панелей администратора, с которыми вы можете работать ежедневно, создаются с использованием SPA.
Преимущества SPA:
- Пользовательский опыт стал более гибким
- Кэширует данные в браузере
- Быстрое время загрузки
Недостатки SPA:
- Может поставить под угрозу SEO (поисковую оптимизацию)
- Потенциальные проблемы безопасности
- Потребляет много ресурсов браузера
Настройка проекта
В этом посте будет показано, как разработать приложение для создания дел, которое позволяет пользователям регистрировать учетную запись и добавлять задачи.
В этом уроке используется Laravel 9, который требует PHP 8.1 и Vue 3; нам также необходимо установить PHP и NGINX .
Начнем со следующей команды:
composer create-project --prefer-dist laravel/laravel laravel-vue-demo
Далее мы установим зависимости JavaScript.
npm install
Нам необходимо установить несколько пакетов, прежде чем мы сможем добавить Vue в наш проект.
Кроме того, необходимо установить plugin-vue, поскольку Laravel 9 поставляется с Vite, а не с webpack-mix, который был предыдущим сборщиком Laravel для JavaScript. Давайте сделаем это сейчас:
npm install vue@next vue - loader@next @vitejs / plugin-vue
Откройте файл с именем vite.config.js
и добавьте vue()
в конфиг:
import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [ vue(), laravel([ 'resources/css/app.css', 'resources/js/app.js', ]), ], });
Отредактируйте файл app.js
и фрагмент загрузочного файла приложения Vue 3:
require('./bootstrap'); import {createApp} from 'vue'; import App from './App.vue'; createApp(App).mount("#app");
Создайте файл с именем App.vue
и добавьте следующее:
<template> <h1> Hello, Vuejs with Laravel </h1> </template> <script> export default { setup() { } } </script>
Наконец, откройте файл welcome.blade.php
, расположенный в папке resources/views
, и добавьте следующее:
<!DOCTYPE html> <html> <head> .... @vite('resources/css/app.css') </head> <body> <div id="app"></div> @vite('resources/js/app.js') </body> </html>
Чтобы просмотреть наше приложение, нам нужно запустить наше приложение Vue и сервер Laravel на двух разных терминалах/командных строках:
npm run dev php artisan serve
Чтобы создать наше приложение для дел, нам нужно создать еще несколько файлов. Vue создаст несколько страниц, в основном:
- Страница входа
- Страница регистрации
- Домашняя страница
Для связи с конечными точками Laravel нам нужно установить Axios:
npm install axios
Маршрутизация Vue
Используя пакет vue-router в Vue можно использовать различные стратегии маршрутизации, эти стратегии также известны как history modes .
Когда пользователь запрашивает такие маршруты http://localhost:8000/home
, которые возвращают ошибку 404 при обновлении страницы, мы можем положиться на то, что Laravel обнаружит любые резервные маршруты, а затем обслужит файл Blade, содержащий наше приложение.
По этой причине мы будем использовать режим HTML5:
Route::get('/{vue_capture?}', function() { return view('welcome'); })->where('vue_capture', '[\/\w\.-]*'); import {createRouter, createWebHistory} from 'vue-router'; const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', component: () => import('./pages/Login.vue') }, { path: '/register', component: () => import('./pages/Register.vue') }, { path: '/home', component: () => import('./pages/Home.vue') } ], })
Из-за простоты проекта мы по сути обрабатываем аутентификацию для страницы входа с помощью Laravel Sanctum , а затем сохраняем наш токен в локальном хранилище.
Чтобы другие запросы были успешными, токен присоединяется к заголовку, что позволяет Laravel идентифицировать пользователя, отправляющего запрос.
Вот как будет выглядеть наша страница входа:
А вот как будет выглядеть наша страница регистрации:
Наконец, вот соответствующие блоки кода для обоих:
Login.vue:
<template> <div class="mx-auto w-4/12 mt-10 bg-blue-200 p-4 rounded-lg"> <div class="bg-white shadow-lg rounded-lg px-8 pt-6 pb-8 mb-2 flex flex-col" > <h1 class="text-gray-600 py-5 font-bold text-3xl"> Login </h1> <ul class="list-disc text-red-400" v-for="(value, index) in errors" :key="index" v-if="typeof errors === 'object'"> <li>{{value[0]}}</li> </ul> <p class="list-disc text-red-400" v-if="typeof errors === 'string'">{{errors}}</p> <form method="post" @submit.prevent="handleLogin"> <div class="mb-4"> <label class="block text-grey-darker text-sm font-bold mb-2" for="username" > Email Address </label> <input class="shadow appearance-none border rounded w-full py-2 px-3 text-grey-darker" id="username" type="text" v-model="form.email" required /> </div> <div class="mb-4"> <label class="block text-grey-darker text-sm font-bold mb-2" for="password" > Password </label> <input class="shadow appearance-none border border-red rounded w-full py-2 px-3 text-grey-darker mb-3" id="password" type="password" v-model="form.password" required /> </div> <div class="flex items-center justify-between"> <button class="bg-blue-500 hover:bg-blue-900 text-white font-bold py-2 px-4 rounded" type="submit" > Sign In </button> <router-link class="inline-block align-baseline font-bold text-sm text-blue hover:text-blue-darker" to="register" > Sign Up </router-link> </div> </form> </div> </div> </template> export default { setup() { const errors = ref() const router = useRouter(); const form = reactive({ email: '', password: '', }) const handleLogin = async () => { try { const result = await axios.post('/api/auth/login', form) if (result.status === 200 && result.data && result.data.token) { localStorage.setItem('APP_DEMO_USER_TOKEN', result.data.token) await router.push('home') } } catch (e) { if(e && e.response.data && e.response.data.errors) { errors.value = Object.values(e.response.data.errors) } else { errors.value = e.response.data.message || "" } } } return { form, errors, handleLogin, } } }
Представление/страница Vue Home
обрабатывает все действия, такие как создание, удаление, обновление и составление списка задач. Все действия отправляют запросы к конечным точкам с токеном пользователя для авторизации через Axios.
Давайте посмотрим, как они будут выглядеть, а также соответствующие фрагменты:
Home:
<template> <div class="w-6/12 p-10 mx-auto"> <div class="flex justify-between"> <h1 class="text-2xl"> Todo </h1> <span class="capitalize">Welcome {{ user && user.name }}, <button class="text-orange-500 underline hover:no-underline rounded-md" @click="handleLogout">Logout</button></span> </div> <input type="text" class="p-2 w-64 border rounded-md" v-model="todo" placeholder="Enter your todo"/> <button class="bg-blue-600 text-white px-5 py-2 rounded-md ml-2 hover:bg-blue-400" @click="addTodo">Add</button> <Loader v-if="isLoading"/> <ul class="border-t mt-3 cursor-pointer"> <li :class="`py-3 border-b text-gray-600 ${val.has_completed ? 'line-through' : ''}`" v-for="(val, idx) in todos" :key="idx"> <input type="checkbox" :checked="val.has_completed" @click="checked(idx)"/> <span @click="checked(val, idx)" class="pl-3">{{ val.title }} </span> <button class="float-right bg-red-400 px-2 text-white font-bold rounded-md hover:bg-red-600" @click="deleteTodo(val, idx)">× </button> </li> </ul> </div> </template> setup() { const todo = ref('') const todos = ref([]) const user = ref() const isLoading = ref() let router = useRouter(); onMounted(() => { authentication() handleTodos() }); const authentication = async () => { isLoading.value = true try { const req = await request('get', '/api/user') user.value = req.data } catch (e) { await router.push('/') } } const handleTodos = async () => { try { const req = await request('get', '/api/todos') todos.value = req.data.data } catch (e) { await router.push('/') } isLoading.value = false } const handleNewTodo = async (title) => { try { const data = {title: title} const req = await request('post', '/api/todos', data) if (req.data.message) { isLoading.value = false return alert(req.data.message) } todos.value.push(req.data.data) } catch (e) { await router.push('/') } isLoading.value = false } const handleLogout = () => { localStorage.removeItem('APP_DEMO_USER_TOKEN') router.push('/') } const addTodo = () => { if (todo.value === "") { return alert("Todo cannot be empty"); } isLoading.value = true handleNewTodo(todo.value) todo.value = "" } const checked = async (val, index) => { try { const data = {has_completed: !val.has_completed} const req = await request('put', `/api/todos/${val.id}`, data) if (req.data.message) { isLoading.value = false return alert(req.data.message) } todos.value[index].has_completed = !val.has_completed } catch (e) { await router.push('/') } isLoading.value = false } const deleteTodo = async (val, index) => { if (window.confirm("Are you sure")) { try { const req = await request('delete', `/api/todos/${val.id}`) if (req.data.message) { isLoading.value = false todos.value.splice(index, 1) } } catch (e) { await router.push('/') } isLoading.value = false } }
Для Laravel мы создадим следующее:
- Controllers (
AuthController
,TodoController
) - Models (
Todo
,User
) - Routes (
api
) - Middleware (
auth:sanctum
).
Наши маршруты находятся в api.php
, который обрабатывает все конечные точки, используемые Vue.
Route::post('/auth/register', [AuthController::class, 'register']); Route::post('/auth/login', [AuthController::class, 'login']); Route::apiResource('todos', TodoController::class)->middleware('auth:sanctum'); Route::middleware('auth:sanctum')->get('/user', function (Request $request) { return $request->user(); });
Логика регистрации AuthController
регистрирует пользователя и создает токен.
public function register(Request $request): \Illuminate\Http\JsonResponse { try { //Validated $validateUser = Validator::make($request->all(), [ 'name' => 'required', 'email' => 'required|email|unique:users,email', 'password' => 'required' ]); if($validateUser->fails()){ return response()->json([ 'status' => false, 'message' => 'validation error', 'errors' => $validateUser->errors() ], 401); } $user = User::create([ 'name' => $request->name, 'email' => $request->email, 'password' => Hash::make($request->password) ], 201); return response()->json([ 'status' => true, 'message' => 'User Created Successfully', 'token' => $user->createToken("API_TOKEN")->plainTextToken ], 200); } catch (\Throwable $e) { return response()->json([ 'status' => false, 'message' => $e->getMessage() ], 500); } }
Логин проверяет запрос, проверяет, существует ли пользователь, а затем создает токен:
public function login(Request $request): \Illuminate\Http\JsonResponse { try { //Validated $validateUser = Validator::make($request->all(), [ 'email' => 'required', 'password' => 'required' ]); if($validateUser->fails()){ return response()->json([ 'status' => false, 'message' => 'validation error', 'errors' => $validateUser->errors() ], 401); } if(!Auth::attempt($request->only(['email', 'password']))){ return response()->json([ 'status' => false, 'message' => 'Email & Password does not exist.', ], 401); } $user = User::where('email', $request->email)->first(); return response()->json([ 'status' => true, 'message' => 'Logged In Successfully', 'token' => $user->createToken("API_TOKEN")->plainTextToken ], 200); } catch (\Throwable $e) { return response()->json([ 'status' => false, 'message' => $e->getMessage() ], 500); } }
Конечные точки POST для добавления новых задач управляются методом store
в контроллере задач — ./api/todos
public function store(Request $request): \Illuminate\Http\JsonResponse { $data = Todo::where('user_id', $request->user()->id)->where('title', $request->title); if ($data->first()) { return response()->json(['status' => false, 'message' => 'Already exist']); } $req = $request->all(); $req['user_id'] = $request->user()->id; $data = Todo::create($req); return response()->json(['status' => true, 'data' => $data], 201); }
Он управляет Todo
конечной точкой обновления, которая вызывается после того, как пользователь завершает задачу и находится по адресу ./api/todos/id
public function update(Request $request, $id): \Illuminate\Http\JsonResponse { $validateUser = Validator::make($request->all(), [ 'has_completed' => 'required', ]); if ($validateUser->fails()) { return response()->json([ 'status' => false, 'message' => 'validation error', 'errors' => $validateUser->errors() ], 401); } $data = Todo::find($id); $data->has_completed = $request->has_completed; $data->update(); return response()->json(['status' => true, 'data' => $data], 202); }
Когда пользователь удаляет Todo
, вызывается конечная точка /api/todos/id
, и об этом позаботятся:
public function destroy(int $id): \Illuminate\Http\JsonResponse { throw_if(!$id, 'todo Id is missing'); Todo::findOrFail($id)->delete(); return response()->json(['status' => true, 'message' => 'todo deleted']); }
Отлично, мы создали SPA приложение, используя Laravel и Vue! 🎉
Заключение
Нам удалось установить, насколько проще создать простое приложение для аутентификации пользователя и списка дел с использованием Vue и Laravel по сравнению с традиционной комбинацией PHP/Vue, которая может потребовать гораздо больше работы по настройке.
Комбинация Vue с Laravel упрощает разработку одностраничных приложений, поскольку нет необходимости беспокоиться о маршрутизации, промежуточном программном обеспечении или обработке CORS. Дайте нам знать о своем опыте разработки SPA в разделе комментариев ниже.