Building a vue spa with laravel

Что такое Laravel Elixir?

Перейдём к нашей системе сборки.

Один из основных недостатков React — сложность инструментов для сборки, которые вам нужно изучить для работы с ним. Ранее мы рассмотрели Webpack ( Распаковка Webpack ), но нельзя сказать, что он особенно прост в изучении.

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

Gulp и импорт ES2015 — первые шаги к упрощению нашей жизни. Импорт ES2015 стандартизирует процесс импортирования модулей между нашими компонентами. А Gulp — лучший инструмент для сборки, с которым я когда либо работал (да, я читал эти статьи о том, как сборочные скрипты NPM изменили мир, и я пробовал использовать их, и я бы предпочёл навсегда стереть из своей головы воспоминания об этом кошмаре).

Laravel Elixir — инструмент для сборки для PHP-фреймворка Laravel, но он также прекрасно работает и вне Laravel. Это обёртка вокруг Gulp, которая позволяет просто и безболезненно выполнять все задачи по разработке, которые характерны для подавляющего большинства веб-приложений.

Рассмотрим такой Gulp-файл:

А вот тот же файл при использовании Elixir:

Самое важное — мы получаем Babel (для ES2015) бесплатно, а Vueify недорого, поэтому можем писать простые, раздельные компоненты и не иметь проблем, когда дело доходит до их соединения воедино.

Итак, давайте сделаем это.

У Elixir большой список зависимостей, по причине его больших возможностей. Если вы профессиональный Vue-разработчик, вы можете собрать собственные более легковесные gulpfile.js и package.json. Но я рекомендую не делать этого, пока вы не найдёте очень вескую причину.

Импортирование пакетов

Если это Laravel-проект, то у вас уже есть Elixir, и вы можете пропустить многие из этих начальных шагов. Основная необходимая вам настройка — добавить laravel-elixir-vueify в ваш package.json и установить его, и добавить задачу browserify в ваш gulpfile.js и указать его в файле app.js. Всё остальное практически совпадает.

Откройте ваш package.json и заполните его чем-то таким:

Где же в этом списке Vue и Vueify? Мы используем плагин для Elixir под названием laravel-elixir-vueify, который подключает их в качестве зависимостей.

Теперь сохраним его и установим наши пакеты:

Это займёт какое-то время, поэтому давайте начнём писать наш Gulp-файл. Создайте файл с именем gulpfile.js и вставьте в него это:

Данный файл app.js предполагает, что исходники будут в resources/assets/js/app.js, а результат будет в public/js/app.js. Это можно настроить позже, а пока давайте просто создадим эти файлы/папки:

Давайте запустим его в первый раз (при этом для вас будет создана целевая папка):

Теперь у вас есть (практически) пустой файл app.js в public/assets/js. В этот файл вы будете включать свой HTML.

Вставка в HTML-страницу

Давайте скорее создадим компонент Vue и вставим его в нашу страницу. Сначала создадим public/index.html:

И включим его в наш resources/assets/js/app.js:

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

Теперь вы можете открыть index.html в браузере, и там вы… ничего не увидите. Скоро мы это изменим.

Создание своего первого компонента

Давайте создадим компонент для нашей страницы. Начнём с профиля пользователя.

Сначала создадим файл с именем Profile.vue в resources/assets/js. Заполним разделы компонента:

Теперь поместим этот компонент в app.js:

И наконец, используем компонент в index.html:

Сохраняем, даём запуститься нашему gulp, и неожиданно наш компонент появляется на странице:

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

Перед тем как закончить, рассмотрим использование в компоненте.

Включение компонентов в другие компоненты

Создадим небольшой компонент для изображения профиля. Добавим новый файл в resources/assets/js/Picture.vue:

Теперь импортируем его в Profile.vue:

Та-дам! Наше изображение теперь показано на профиле.

Наслаждайтесь! Развивайтесь с vueify.

Установка

Требования к серверу

Фреймворк Laravel предъявляет некоторые системные требования. Конечно же, виртуальная машина Laravel Homestead соответствует всем этим требованиям, поэтому настоятельно рекомендуется использовать Homestead в качестве основной локальной среды разработки с Laravel.


Однако, если вы не используете Homestead, вам необходимо убедиться, что ваш сервер соответствует следующим требованиям:

  • PHP >= 5.6.4
  • Расширение PHP OpenSSL
  • Расширение PHP PDO
  • Расширение PHP Mbstring
  • Расширение PHP Tokenizer
  • Расширение PHP XML

Установка Laravel

Laravel использует Composer для управления своими зависимостями, поэтому убедитесь в том, что Composer установлен на вашей машине.

С помощью установщика Laravel

Сначала скачайте установщик Laravel с помощью Composer:

Проверьте, чтобы директория (или аналогичная в зависимости от вашей ОС) находилась в переменной $PATH, что позволит вашей системе найти и выполнить команду .

После установки команда создаёт свежую установку Laravel в указанной вами директории. Например, создаст директорию с названием , которая будет содержать свежую установку Laravel со всеми зависимостями:

Локальный сервер разработки

Если локально у вас уже установлен PHP и вы хотели бы использовать встроенный сервер для работы вашего приложения, то вы можете использовать команду Artisan . Эта команда запустит сервер разработки по адресу :

Конечно же, Homestead и Valet предоставляют наиболее надежные способы локальной разработки.

Настройка

Общедоступная директория

После установки Laravel вам следует указать директорию в качестве корневой директории вашего веб-сервера. Файл в этой категории выступает в роли фронт-контроллера всех HTTP-запросов, поступающих в ваше приложение.

Файлы настройки

Все файлы настройки фреймворка Laravel расположены в директории . Параметры в каждом из них снабжены комментариями, поэтому не стесняйтесь пройтись по этим файлам и познакомиться с доступными параметрами настройки.

Права доступа на директории

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

Ключ приложения

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

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

Дополнительная настройка

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

Вы также можете настроить некоторые дополнительные компоненты Laravel, такие как:

Usage

importVuefrom'vue'importVueAxiosfrom'vue-axios'importVueSocialauthfrom'vue-social-auth'importaxiosfrom'axios';Vue.use(VueAxios, axios)Vue.use(VueSocialauth,{  providers{    github{      clientId'',      redirectUri'/auth/github/callback'}}})
<script>exportdefault{data(){return{}},       methods{AuthProvider(provider){var self =thisthis.$auth.authenticate(provider).then(response=>{self.SocialLogin(provider,response)}).catch(err=>{console.log({errerr})})},SocialLogin(provider,response){this.$http.post('/sociallogin/'+provider,response).then(response=>{console.log(response.data)}).catch(err=>{console.log({errerr})})},}}<script>
{          path'/auth/:provider/callback',          component{            template'<div class="auth-component"></div>'}},
Route::post('sociallogin/{provider}', 'Auth\AuthController@SocialSignup');Route::get('auth/{provider}/callback', 'OutController@index')->where('provider', '.*');
<?phpnamespaceApp\Http\Controllers;useIlluminate\Http\Request;classOutControllerextendsController{publicfunction__construct(){}publicfunctionindex(){returnview('welcome');}}
<?phpnamespaceApp\Http\Controllers\Auth;useApp\Http\Controllers\Controller;useIlluminate\Http\Request;useSocialite;classAuthControllerextendsController{publicfunction__construct(){}publicfunctionSocialSignup($provider){$user=Socialite::driver($provider)->stateless()->user();returnresponse()->json($user);}}

you may need to disable Csrf for the route if you receive

Phase 2. Database layer and API

Next step – is to take care of managing our CRUD.

Step 1. Model and database. Launch this:

It will create a file app/Company.php which you would fill with this:

And same fields in a migration file that has been generated:

Now, let’s create a Controller that will manage all CRUD operations. But it won’t be a simple make:controller command – we have to save it as API thing, something like this:

So command would look like this – with full path:

And we fill it with a typical CRUD operation list:

Finally, we need to take care of the routing – in our routes/api.php file:

As you can see, I’m adding a prefix api. and excluding create/edit methods cause they don’t make sense without visual forms for API.

Important notice: for this tutorial, I didn’t build any authentication mechanism for the API, in real life you should protect your routes with some middleware or Laravel Passport. Actually, we have a separate tutorial and demo-project for that.

Ok, we should be done with API layer by now, you can try it with Postman or some other client. Now, let’s get (finally!) to Vue.js layer.

Set up Vuex store


We’ll be using the Vuex library to centralize our data and control the way it is mutated throughout our application.

Create our state

Vuex state is a single object that contains all our application data. So let’s create and paste this code inside:

The code above is straightforward. The key is an array responsible to store our database products

Create our mutations

Mutations allow us to perform some changes on our data. Create and paste this piece of code inside:

Our mutations object has a function with two arguments and ; this function assigns the array to our state products key.

Create our actions

Vuex actions allow us to perform asynchronous operations over our data. Create the file and paste the following code:

We have defined two actions and each of them responsible of a single operation, either products search or products search. They both perform asynchronous calls to our API routes.

  • sends a get request to our endpoint to get products. This action is dispatched whenever the user is searching for something.

  • makes a get request to our endpoint to get our database products and commits the request result with mutation.

Set up our store with Vue

Create the file and paste this code inside:

Then, we export our store and add it to the Vue instance. Add this code to your file.

The previous code also globally registers three Vue components, , and that we’ll build in the next part of this tutorial.

Роуты

Давайте начнём с создания роутов. Поскольку это CRUD соответственно у нас должно быть 7 роутов типа:

Route::get('notes', 'NotesController@index');
Route::get('notes/create', 'NotesController@create');
Route::get('notes/store', 'NotesController@store');
Route::get('notes/{id}/edit', 'NotesController@edit');
Route::get('notes/{id}/update', 'NotesController@update');
Route::get('notes/{id}/show', 'NotesController@show');
Route::get('notes/{id}/destroy', 'NotesController@destroy');

Для тех кто не в курсе роуты прописываются в файле routes/web.php

Но право же, для каждой сущности плодить по 7 роутов это неслыханное кощунство! Да и тем более таких типичных. Давайте мы научимся записывать их все одной строкой, вот так например:

Route::resource('notes', 'NotesController');

И всё! Больше нам ничего писать в роутах не нужно, потому, что они сами создадутся. Не верите? Давайте сами в этом убедимся. Вам всего лишь нужно запустить в терминале команду: php artisan route:list что бы увидеть все доступные пути в текущем приложении.

Должно получится вот такое великолепие со всеми путями алиасами и методами:

| Method    | URI                | Name          | Action                                      | Middleware
| POST      | notes              | notes.store   | App\Http\Controllers\NotesController@store  | web |
| GET|HEAD  | notes              | notes.index   | App\Http\Controllers\NotesController@index  | web |
| GET|HEAD  | notes/create       | notes.create  | App\Http\Controllers\NotesController@create | web |
| PUT|PATCH | notes/{note}       | notes.update  | App\Http\Controllers\NotesController@update | web |
| GET|HEAD  | notes/{note}       | notes.show    | App\Http\Controllers\NotesController@show   | web |
| DELETE    | notes/{note}       | notes.destroy | App\Http\Controllers\NotesController@destroy| web |
| GET|HEAD  | notes/{note}/edit  | notes.edit    | App\Http\Controllers\NotesController@edit   | web |

Ну осталось только взять да и создать модель, контроллер и все эти методы.

License

The MIT License (MIT)

Copyright (c) 2018 Diadal Nig LTD

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the «Software»), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED «AS IS», WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Quick start

Do you want to test the application quickly, and you do not have an API ready? No problem, you can use the ready-made example in the examples folder. The API for this example is available on the internet, so you can connect to it by entering its address in the configuration file.

Steps

  1. Clone Vue CRUD:
git clone git@github.com:what-crud/vue-crud.git
  1. Type following commands:
yarn
:: or
npm install
  1. Choose one of the following templates:
  • ,
  • ,
  • ,
  • ,

…and type e.g.:

yarn load-template simple-crud
:: or
npm run load-template simple-crud
  1. If you have your own API prepared, modify src/config/api.js file.

  2. Serve your app:

yarn serve
:: or
npm run serve

Usage

The simplest possible code that supports CRUD operations for one table will look something like this:

<template>
  <div>
    <crud
      :prefix="prefix"
      :path="path"
      :page-title="pageTitle"
      :fields-info="fieldsInfo"
      :details-title="$t('detailsTitle')"
    >
    </crud>
    <alert-box></alert-box>
  </div>
</template>

<script>
  import Crud from '@/utils/crud/components/Crud.vue'
  import AlertBox from "@/utils/app/components/AlertBox.vue";
  export default {
    data() {
      return {
        prefix: 'demo',
        path: 'tasks',
        pageTitle: 'demo.tasks',
      }
    },
    computed: {
      fieldsInfo() {
        return {
            text: this.$t('fields.id'),
            name: 'id',
            details: false,
          },
          {
            type: 'input',
            column: 'name',
            text: this.$t('fields.name'),
            name: 'name',
            multiedit: false
          },
          {
            type: 'input',
            column: 'description',
            text: this.$t('fields.description'),
            name: 'description',
            required: false
          },
        
      },
    },
    components: {
      Crud,
      AlertBox,
    },
    i18n: {
      messages: {
        en: {
          detailsTitle: 'Task',
          fields: {
            id: 'Id',
            name: 'Name',
            description: 'Description'
          }
        }
      }
    },
  }
</script>

CometChat’s authentication flow

It is compulsory that one must be authenticated as a user before you can make use of infrastructure made available by CometChat.


As proof of concept or to test a demo application, you can make use of the sample users that are being generated automatically for every new application created on CometChat. But for a production-ready application, it is advisable to take it a step further by making use of our REST API to programmatically create unique users, as you would see later in the tutorial.

Once a user is in place, authenticating such a user to CometChat, is as simple as calling the the method from the JavaScript SDK. Before this method can work properly, you can either use the user’s UID and the auth-only secret key as a parameter for the , or you can generate a unique auth token on CometChat via its REST API and use the generated token as they require a parameter.

For the purpose of this tutorial, you will learn how to use the auth token for authenticating a user.

Building models and seeding our database

If you already worked with Laravel you know that it has a great command-line tool to generate models and so on. First run this command:

This command tells Laravel to generate a Post model for us, the flags indicates that it should also generate migration as well as a controller named . We’ll take a look at these files further in the tutorial.

Next, copy and paste this piece of code into your post migration file

Now, run to create the table in your database with the corresponding fields.

Having our database functional we can begin adding some data but it can be tiresome. So let’s seed our database with Laravel database seeding functionnality.

Execute this command to generate a factory for our Post model. Next copy and paste the following code inside our file

The above code defines a set of attributes for our model with fake data as you can notice, and the code is self-explanatory. Then paste this code inside your file:

So what it means ? You may have guessed it, it tells Laravel to generate 15 instances of our Post model.

And finally run the following command: to make Laravel seed the database with the factory we define. If you check up your database you should see 15 fresh rows in your posts table. Great isn’t it !?

What we will build

At the end of this tutorial, you would have built a system that will allow users to send and receive messages in realtime, as shown below:

To join a chat session, a user will have to provide a unique and as credentials during the registration process, afterward, such user will be created on CometChat as well. This means when registering a new user within your application, the details of such users will be saved in your local database first and then sent to the CometChat server using its REST API. Laravel will be used to build a backend API needed for these processes and make provisions of endpoints for Vue.js on the frontend.

If you would love to try out the working demo for this tutorial, you can download the complete source code here on GitHub.

Building models and seeding our database

Now, let’s build our database structure. We’ll use again Laravel CLI for that. Run this command:

The above command will generate the Product model as well as its migration and its controller for us.

Open your file and paste this:

Next copy and paste this piece of code in your product migration file:

Then run to run the migration.

Now, we’ll seed our database to avoid having to populate it manually because it can be tedious.

Execute this command to generate a factory for our Product model. Next copy and paste the following code inside our file

Our sets a value for each of our Product model field as you can see in the above code. The last step in this section is to tell Laravel to use our . Let’s do that, paste this code inside your file:

We generate 25 instances of our Product model. Finally run this command so that Laravel can seed the database with the factory we define: .

If you check your database, you can see that your database has been populated as well and it contains 25 rows. Great isn’t it!

Defining routes and creating the ProductController

In this section we’ll define our app endpoints and define the logic behind our .

Let’s create a route named (which will be called when the user attempts to search for products), and another route named to fetch our products from database. Paste the following into :

We should also define a get route named to return our app view. Copy this code and replace the existing one inside your file:

Now let’s define our controller logic. Our controller functions will be responsible for actions to handle when some requests reach our API endpoints.

Open your file and paste the following code:

In the above code we have two functions and :

  • — this function returns all existing posts in our database
  • — this function is a bit tricky. It gets the query sent in the request and returns every product whose name or description contains it. This is handled there:

Emit event

Well you may have noticed this line: . What is its purpose? It broadcasts an event with search results to the client-side of our app using Laravel broadcasting. We’ll see how to create this event in the next part of the tutorial.

Quick overview of server-side rendering

If you aren’t familiar with server-side rendering (SSR), here’s a simple example: say we have an Vue.js app built with components. If we use the browser dev tools to view the page DOM after the page has loaded, we will see our fully rendered app:


But if we view the source of the document i.e. index.html as it was when sent by the server, you’ll see it just has our mount element:

Why the discrepency? Because JavaScript is responsible for building the page, and ipso facto, JavaScript has to run before the page is built. Fresh off the server, the page will have no content.

But with server-side rendering, our page includes the HTML needed for the browser to build a DOM before JavaScript is downloaded and run, i.e. the page source would look like the first example above. This is achieved by running the Vue.js app on the server and capturing the output, then injecting that output into the page before it is sent to the user.

With SSR, your app does not load or run any faster, indeed it may run slightly slower as the server has the added task of rendering the app. But the page content is shown sooner therefore the user can see engage with page sooner.

Defining routes and controller functions

In this part we’ll define the routes that our app should call to access our data , as well as the proper controller responsible to handle the logic for us.

First paste this code in your file. It means that function should be called whenever a get request is made to routes. And then open your file and paste this :

The above piece of code defines our routes and which function should handle them. Basically the first line is saying that for the routes with a post request, the function of our should handle the logic and so on. Now let’s create the corresponding functions in our controller. Paste the following code in your class body.

Well, let’s take a minute to explain this code bock.

  • returns a view where should be listed all our posts
  • returns all posts existing in our database
  • creates a Post instance an returned it as a JSON response
  • destroys as you can guess a post provided its ID is given

Now let’s focus on the frontend part of our fullstack app. We’ll build here our Vue.js components, manage state with Vuex and handle our requests with the Axios library

Implementing CRUD in the Vue SPA with AJAX

All the CRUD operations in a full-stack app will be executed in the backend since that’s where the database is. However, the triggering of CRUD operations will happen in the Vue SPA.

As such, an HTTP client (something that can communicate between our front and backends across the internet) will be of importance here. Axios is a great HTTP client that comes pre-installed with the default Laravel frontend.

Let’s look at our resource table again, as each AJAX call will need to target a relevant API route:

Verb Path Action Route Name
GET /api/cruds index cruds.index
GET /api/cruds/create create cruds.create
PUT /api/cruds/{id} update cruds.update
DELETE /api/cruds/{id} destroy cruds.destroy

Read

Let’s begin with the method. This method is responsible for retrieving our Cruds from the backend and will target the action of our Laravel controller, thus using the endpoint .

We can set up a GET call with , as the Axios library has been aliased as a property of the object in the default Laravel frontend setup.

Axios methods like , , etc return a promise. We can use async/await to neatly the response object. We’ll destructure it so we can grab the property which is the body of the AJAX response.

resources/assets/js/components/App.vue

As you can see, the Cruds are returned in a JSON array. Axios automatically parses the JSON and gives us JavaScript objects, which is nice. Let’s iterate through these and create new Cruds with our factory function, pushing each new one to the array data property.

Finally, we’ll trigger this method programmatically from the hook, ensure our Cruds get added when the page first loads.

resources/assets/js/components/App.vue

With that done, we can now see the Cruds displayed in our app when we load it:

Update (and syncing state)

The action allows us to change the color of a Crud. We’ll send form data to the API endpoint so it knows what color we want to use. Note that the ID of the Crud is provided in the URL as well.

This is a good time to discuss an issue I mentioned at the beginning of the article: with single-page apps, you must ensure the state of the data is consistent in both the front and backends.

In the case of the method, we could update the Crud object in the frontend app instantly before the AJAX call is made since we already know the new state.

However, we don’t perform this update until the AJAX call completes. Why? The reason is that the action might fail for some reason: the internet connection might drop, the updated value may be rejected by the database, or some other reason.

If we instead wait until the server responds before updating the frontend state, we can be sure the action was successful and the front and backend data is synchronized.

resources/assets/js/components/App.vue

Create and Delete

Now that you understand the key points of the architecture, you will hopefully be able to understand these last two operations without my commentary:

resources/assets/js/components/App.vue


С этим читают