Creating new Laravel project LiveJournal2020 (Rest API for posting articles).

Introduction

This article will describe the process of creating a LiveJournal web application in which you can create, edit, view and delete articles.

The server side of the application uses Php and the Laravel web framework. The client part is built on the JavaScript framework Vue.js. To write the code, the text editor Sublime Text 3 was used.

At the very beginning of creating any application, you need to prepare a working environment. After that, you can begin to develop.

First you need to create the application database. The next step is to create a Laravel project, install all the dependencies with the command:

composer install

And also generate a key for the project using the command:

php artisan key: generate

Now you need to add the .env file to the project, which stores all the necessary environment variables. Following the example in the form of the .env.example file, here you need to enter the data necessary to connect to the project database and also specify the URL address of the web application.

Backend part of the project

The project will use resource controllers. First you need to create such a controller using the command:

php artisan make:controller ArticleController --resource

And register the resource route to the controller in the api.php file which is located in the routes folder:

Route::resources([
'articles' => 'ArticleController'
]);

To create a convenient and easily scaled architecture, you will need to create 2 more application layers. The first will be used to work with business logic, the second – to work with the database.

Now the new controller needs to be associated with the service layer.

There will be 5 methods in the controller:
– store method for creating an article
– show method for article pagination
– edit method to obtain the data of the desired article
– update method to edit the article
– destroy method to delete an article

namespace App\Http\Controllers;
 
use App\Http\Requests\StoreNews;
use App\Http\Requests\UpdateNews;
use App\Services\Article\ArticleService;
use App\Http\Resources\ArticleResource;
use Illuminate\Http\Request;
 
class ArticleController extends Controller
{
    /**
     * ArticleService class object
     *
     * @var object
     */
    protected $articleService;
 
    /**
     * Initialize class properties
     *
     * @param ArticleService $articleService
     */
    public function __construct(ArticleService $articleService)
    {
        $this->articleService = $articleService;
    }
 
    /**
     * Store a newly created resource in storage.
     *
     * @param  \App\Http\Requests\StoreNews  $request
     * @return \Illuminate\Http\Response
     */
    public function store(StoreNews $request)
    {
        $validated = $request->validated();
        $this->articleService->create($validated);
        return response()->json('Article has been created succesfully', 200);
    }
 
    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        $data = $this->articleService->paginate();
        return response()->json($data, 200);
    }
 
    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        $data = $this->articleService->getById($id);
        return new ArticleResource($data);
    }
 
    /**
     * Update the specified resource in storage.
     *
     * @param  \App\Http\Requests\UpdateNews  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(UpdateNews $request, int $id)
    {
        $validated = $request->validated();
        $this->articleService->update($validated, $id);
        return response()->json('Article has been updated succesfully', 200);
    }
 
    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        $this->articleService->delete($id);
        return response()->json('Deleted successfully', 200);
    }
}

All imports except Request are not yet in the project, but in the future they will be created. StoreNews will be needed to validate data when creating an article, and UpdateNews when editing it. ArticleService is an article service in which business logic will be processed. ArticleResource is needed to convert the model to JSON. Using the Dependency Injection pattern, a service layer was associated with the current controller.

Next, create a StoreNews request using the command:

php artisan make:request StoreNews

And write validation rules:

public function rules()
{
    return [
      'title' => 'unique:articles|required|max:30|min:3',
      'subtitle' => 'required|max:100|min:3',
      'content' => 'required'
    ];
}

Сreate an UpdateNews file and its rules should be like that:

public function rules()
{
    return [
            'title' => [
                'required',
                'max:30',
                'min:3',
                Rule::unique('articles')->ignore($this->article)
            ],
            'subtitle' => 'required|max:100|min:3',
            'content' => 'required'
    ];
}

You also need to create the resource with the command:

php artisan make:resource ArticleResource

And change its method toArray:

public function toArray($request)
{
    return [
        'id' => $this->id,
        'title' => $this->title,
        'subtitle' => $this->subtitle,
        'content' => $this->content,
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at
    ];
}

The next step is to create services. You need to create the Services folder in the app folder. This folder will contain the AbstractInterface.php file. This will be the interface that describes the basic methods of the service class:

namespace App\Services;
 
interface AbstractInterface
{
    /**
     * Describe class create method
     *
     * @param  array $data
     */
    public function create(array $data);
 
    /**
     * Describe class paginate method
     */
    public function paginate();
 
    /**
     * Describe class update method
     *
     * @param  array  $data
     * @param  int    $id
     */
    public function update(array $data, int $id);
 
    /**
     * Describe class delete method
     *
     * @param  int $id
     */
    public function delete(int $id);
 
    /**
     * Describe class getById method
     *
     * @param  int $id
     */
    public function getById(int $id);
}

Here we also create an AbstractService.php file, in which there will be a class that implements the abstract interface. This class will have 2 properties that are needed to work with project repositories and indicate the number of entries on the pagination page:

namespace App\Services;
 
class AbstractService implements AbstractInterface
{
    /**
     * Repository class object
     *
     * @var object
     */
    protected $repository;
    /**
     * Count of rows on pagination page
     *
     * @var int
     */
    protected $paginationRowsCount;
 
    /**
     * Call repository create method
     * @param  array $data
     * @return void
     */
    public function create(array $data)
    {
        $this->repository->create($data);
    }
 
    /**
     * Call repository paginate method
     *
     * @return object
     */
    public function paginate()
    {
        return $this->repository->paginate($this->paginationRowsCount);
    }
 
    /**
     * Call repository delete method
     *
     * @param  int $id
     * @return void
     */
    public function delete(int $id)
    {
        $this->repository->delete($id);
    }
 
    /**
     * Call repository getById method
     *
     * @param  int $id
     * @return object
     */
    public function getById(int $id)
    {
        return $this->repository->getById($id);
    }
 
    /**
     * Call repository update method
     *
     * @param  array $data
     * @param  int $id
     * @return void
     */
    public function update(array $data, int $id)
    {
        $this->repository->update($data, $id);
    }
}

Then in the services folder you need to create the Article folder, where the following files will be stored:
– ArticleInterface.php

namespace App\Services\Article;
 
use App\Services\AbstractInterface;
 
interface ArticleInterface extends AbstractInterface
{
}

– ArticleService.php

namespace App\Services\Article;
 
use App\Repositories\Article\ArticleRepository;
use App\Services\AbstractService;
 
class ArticleService extends AbstractService implements ArticleInterface
{
    /**
     * Initialize class properties
     *
     * @param ArticleRepository $articleRepository
     */
    public function __construct(ArticleRepository $articleRepository)
    {
        $this->repository = $articleRepository;
        $this->paginationRowsCount = 3;
    }
}

After creating the services, create the Repositories folder in the app folder, the Article folder should be here, the structure for the repositories will be the same as the services. There will be 2 files in this folder:
– AbstractInterface.php

namespace App\Repositories;
 
interface AbstractInterface
{
    /**
     * Describe class create method
     *
     * @param  array $attributes
     */
    public function create(array $attributes);
    /**
     * Describe class paginate method
     *
     * @param  int $rowsCount
     */
    public function paginate(int $rowsCount);
    /**
     * Describe class delete method
     *
     * @param  int $id
     */
    public function delete(int $id);
    /**
     * Describe class getById method
     *
     * @param  int $id
     */
    public function getById(int $id);
    /**
     * Describe class update method
     *
     * @param  array $attributes
     * @param  int $id
     */
    public function update(array $attributes, int $id);
}

– AbstractRepository.php

namespace App\Repositories;
 
use App\Jobs\SaveArticle;
 
class AbstractRepository implements AbstractInterface
{
    /**
     * Model object
     *
     * @var object
     */
    protected $model;
 
    /**
     * Call SaveArticle job
     *
     * @param  array  $attributes
     * @return void
     */
    public function create(array $attributes)
    {
        dispatch(new SaveArticle($attributes));
    }
 
    /**
     * Paginate rows
     *
     * @param  int $rowsCount
     * @return object
     */
    public function paginate(int $rowsCount)
    {
        return $this->model->paginate($rowsCount);
    }
 
    /**
     * Delete row in database
     *
     * @param  int $id
     * @return void
     */
    public function delete(int $id)
    {
        $item = $this->model->findOrFail($id);
        $item->delete();
    }
 
    /**
     * Find row in database by id
     *
     * @param  int $id
     * @return object
     */
    public function getById(int $id)
    {
        return $this->model->findOrFail($id);
    }
 
    /**
     * Update row in database
     *
     * @param  array $attributes
     * @param  int $id
     * @return void
     */
    public function update(array $attributes, int $id)
    {
        $object = $this->model->findOrFail($id);
        $object->update($attributes);
    }
}

Then you need to create the Article folder, which will also contain 2 files:
– ArticleInterface.php

namespace App\Repositories\Article;
 
use App\Repositories\AbstractInterface;
 
interface ArticleInterface extends AbstractInterface
{
}

– ArticleRepository.php

namespace App\Repositories\Article;
 
use App\Repositories\AbstractRepository;
use App\Models\Article;
 
class ArticleRepository extends AbstractRepository implements ArticleInterface
{
    /**
     * Initialize class properties
     *
     * @param Article $article
     */
    public function __construct(Article $article)
    {
        $this->model = $article;
    }
}

In AbstractRepository.php, the article is saved to the database using the Laravel Job. To do this, create a Job with the command:

php artisan make:job SaveArticle

And write the required functionality in it:

namespace App\Jobs;
 
use App\Models\Article;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
 
class SaveArticle implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
    /**
     * Array of attributes
     *
     * @var array
     */
    protected $attributes;
 
    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(array $attributes)
    {
        $this->attributes = $attributes;
    }
 
    /**
     * Execute the job. Create article in database.
     *
     * @return void
     */
    public function handle(Article $article)
    {
        $article->create($this->attributes);
    }
}

Next, create an Article model in the app/Models:

namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Article extends Model
{
    /**
     * Array of fillable database column
     *
     * @var array
     */
    protected $fillable = [
        'title', 'subtitle', 'content'
    ];
}

Now to work with the database you need to create migrations:
– migration create_articles_table

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
 
class CreateArticlesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title')->unique();
            $table->string('subtitle');
            $table->string('content');
            $table->timestamps();
        });
    }
 
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('articles');
    }
}

– create_jobs_table migration with the command:

php artisan queue:table

To fill the database, you also need to create seeders. The article factory will be used in the seeders, so first you need to create an ArticleFactory with the command:

php artisan make:factory ArticleFactory
/** @var \Illuminate\Database\Eloquent\Factory $factory */
 
use App\Models\Article;
use Illuminate\Support\Str;
 
/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| This directory should contain each of the model factory definitions for
| your application. Factories provide a convenient way to generate new
| model instances for testing / seeding your application's database.
|
*/
 
 
$factory->define(Article::class, function () {
	$rand = rand(0, 999999);
    return [
        'title' => "testTitle$rand",
        'subtitle' => "testSubtitle$rand",
        'content' => "testContent$rand",
    ];
});

After that, you need to create seeder with the command:

php artisan make:seeder DatabaseSeeder

And use the factory ArticleFactory in it:

use Illuminate\Database\Seeder;
 
class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        factory(App\Models\Article::class, 10)->create();
    }
}

At this stage, the main part of the application backend is ready.

Frontend part of the project

Now you need to install Vue Cli, create the frontend folder with the vue create client command, select the default settings when creating. If during development warnings related to eslint appear, then they can be temporarily disabled using the .eslintignore file in the root of the frontend. This project will use the vue-router, axios to send requests to the server, vue-i18n to create multilingualism, and vuex to create storage. You need to install these plugins with the command:

npm install vue-router axios vue-i18n vuex

Now you need to go to the src folder. Change the main.js file to the requirements of the project:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import VueCookie from "vue-cookie";
import VueI18n from "vue-i18n";
import messages from "./lang";
Vue.use(VueCookie);
 
Vue.config.productionTip = false;
 
Vue.use(VueI18n);
export const i18n = new VueI18n({
  locale: "ru",
  fallbackLocale: "ru",
  messages
});
 
new Vue({
  router,
  store,
  i18n,
  render: h => h(App)
}).$mount("#app");

The project repository should be located in the store folder, in the index.js file:

import Vue from "vue";
import Vuex from "vuex";
 
Vue.use(Vuex);
 
export default new Vuex.Store({
  state: {
    langs: ["ru", "en"]
  },
  getters: {
    LANGS: state => {
      return state.langs;
    }
  },
  mutations: {},
  actions: {}
});

A list of site languages ​​will be stored here.

For multilingualism, you need to create the lang folder. This folder will contain the index.js file:

import ru from "./translations/ru";
import en from "./translations/en";
export default {
  ru,
  en
};

Also here you also need to create a translations folder in which there will be 2 more files:
– en.json

{
  "HomeLink": "Home",
  "Login": "Login",
  "Register": "Register",
  "Dashboard": "Dashboard",
  "LogoutLink": "Logout",
  "Homepage": "Homepage",
  "Dashboard": "Dashboard",
  "Articles": "Article",
  "Title": "Title",
  "Subtitle": "Subtitle",
  "Body": "Body",
  "Save": "Save",
  "Edit": "Edit",
  "Delete": "Delete",
  "First": "First",
  "Last": "Last",
  "EditPage": "Edit Page",
  "ErrorPage": "Error Page"
}

– ru.json

{
  "HomeLink": "Главная",
  "Login": "Войти",
  "Register": "Регистрация",
  "Dashboard": "Добавить новость",
  "LogoutLink": "Выйти",
  "Homepage": "Главная страница",
  "Dashboard": "Страница создания",
  "Articles": "Статья",
  "Title": "Заголовок",
  "Subtitle": "Подзаголовок",
  "Body": "Содержание",
  "Save": "Сохранить",
  "Edit": "Редактировать",
  "Delete": "Удалить",
  "First": "Первая",
  "Last": "Последняя",
  "EditPage": "Страница редактирования",
  "ErrorPage": "Страница ошибки"
}

These files will contain word translations.

Next, create a router folder in which the index.js file will be:

import Vue from "vue";
import Router from "vue-router";
import RouterView from "../components/RouterView.vue";
import Homepage from "../components/Homepage.vue";
import Dashboard from "../components/Dashboard.vue";
import EditPage from "../components/EditPage.vue";
import ErrorPage from "../components/Error.vue";
import { i18n } from "../main.js";
 
Vue.use(Router);
 
export default new Router({
  mode: "history",
  routes: [
    {
      path: "/:lang",
      component: RouterView,
      beforeEnter(to, from, next) {
        const lang = to.params.lang;
        if (!["en", "ru"].includes(lang)) return next("ru");
        if (i18n.locale !== lang) {
          i18n.locale = lang;
        }
        return next();
      },
      children: [
        {
          path: "dashboard/page=:num",
          name: "Dashboard",
          component: Dashboard,
          props: true
        },
        {
          path: "article/edit/:id",
          name: "EditPage",
          component: EditPage,
          props: true
        },
        {
          path: "/",
          name: "Homepage",
          component: Homepage,
          props: true
        }
      ]
    },
    {
      path: "*",
      name: "ErrorPage",
      component: ErrorPage
    }
  ]
});

The routing of a multilingual site is configured here.

Now it remains to create the pages of the site themselves, breaking them into components. You need to go to src/components. This folder will contain the following files:

– component RouterView.vue:

<template>
  <router-view></router-view>
</template>

It is necessary for the correct functioning of the router.

– the Header.vue component, which will be the header of the site, it will also be possible to change the site language in it:

<template>
   <header class="header">
 
 
 
 
 
<div class="left-col">
         <router-link :to="'/' + $i18n.locale">{{ $t("HomeLink") }}</router-link>
</div>
 
 
 
 
 
 
 
 
 
 
<div class="middle-col">
         <span>{{ pageName }}</span>
</div>
 
 
 
 
 
 
 
 
 
 
<div class="right-col">
         <select v-model="$i18n.locale">
 
 
 
 
 
<option v-for="(lang, i) in langs" :key="`Lang${i}`" :value="lang">{{ lang }}
            </option>
 
 
 
 
 
</select>
         <router-link :to="'/' + $i18n.locale + '/dashboard/page=1'">{{ $t("Dashboard") }}
         </router-link>
</div>
 
 
 
 
 
   </header>
</template>
<script>
   export default {
       computed: {
           langs() {
               return this.$store.getters.LANGS;
           }
       },
       props: {
           pageName: {
               type: String,
               default: ""
           }
       }
   };
</script>
<style lang="scss" scoped="">
   @import "@/assets/scss/layout/_AppHeader.scss";
</style>

– component Error.vue to display the error page if the route is incorrect:

<template>
 
 
 
<div>
    <header :pagename="pageName">
 
 
 
<div id="notfound">
 
 
 
<div class="notfound">
 
 
 
<div class="notfound-404">
 
 
 
<h3>Oops! Page not found</h3>
 
 
 
 
 
 
<h1><span>4</span><span>0</span><span>4</span></h1>
 
 
 
</div>
 
 
 
 
 
 
<h2>we are sorry, but the page you requested was not found</h2>
 
 
 
</div>
 
 
 
</div>
 
 
 
    </header>
</div>
 
 
 
 
</template>
<script>
    import Header from "@/components/Header";
 
    export default {
        computed: {
            pageName() {
                return this.$t("ErrorPage");
            }
        },
        components: {
            Header
        }
    };
</script>
 
<style lang="scss" scoped="">
    @import "@/assets/scss/components/_Error.scss";
</style>


– component ArticleSection.vue, which will be a form for editing and creating articles:

<template>
 
 
<div class="article-section">
 
 
<h2 class="headline">{{ $t("Articles") }}</h2>
 
 
    <input v-model="formData.title" type="text" :placeholder="$t('Title')">
 
 
<div v-if="errors.title" class="alert alert-danger" role="alert">
         {{ errors.title[0] }}
</div>
 
 
      <input v-model="formData.subtitle" type="text" :placeholder="$t('Subtitle')">
 
 
<div v-if="errors.subtitle" class="alert alert-danger" role="alert">
         {{ errors.subtitle[0] }}
</div>
 
 
      <textarea v-model="formData.content" :placeholder="$t('Body')"></textarea>
 
<div v-if="errors.content" class="alert alert-danger" role="alert">
         {{ errors.content[0] }}
</div>
 
     <button v-if="isCreateSection" @click="createArticle">
      {{ $t("Save") }}
      </button>
      <button v-if="isEditSection" @click="editArticle">{{ $t("Edit") }}</button>
</div>
 
</template>
<script>
   import axios from "axios";
 
   export default {
     data() {
       return {
         formData: {
           title: "",
           subtitle: "",
           content: ""
         },
         errors: {}
       };
     },
     props: ["isCreateSection", "isEditSection", "article"],
     mounted() {
       if (this.isEditSection) {
         axios
           .get(
             `${process.env.VUE_APP_BASE_URL}/api/articles/${this.$route.params.id}/edit
           )
           .then(response => {
             console.log(`Article ${this.$route.params.id}`, response);
             this.formData.title = response.data.data.title;
             this.formData.subtitle = response.data.data.subtitle;
             this.formData.content = response.data.data.content;
           })
           .catch(error => {
             console.log(error.response.data);
           });
       }
     },
     methods: {
       clearObject(obj) {
         for (let key in obj) {
           obj[key] = "";
         }
       },
       createArticle() {
         this.clearObject(this.errors);
         axios
           .post(`${process.env.VUE_APP_BASE_URL}/api/articles`, this.formData)
           .then(response => {
             for (let key in this.formData) {
               this.formData[key] = "";
             }
             console.log("New article", response);
             this.$emit("create");
             this.$router.push(`/${this.$i18n.locale}/dashboard/page=1`);
           })
           .catch(error => {
             this.errors = error.response.data.errors;
             console.log("Errors", error.response.data.errors);
           });
       },
       editArticle() {
         this.clearObject(this.errors);
         let data = this.formData;
         data._method = "put";
         axios
           .post(
             `${process.env.VUE_APP_BASE_URL}/api/articles/${this.$route.params.id}`,
             data
           )
           .then(response => {
             console.log("Updated article", response);
             this.$router.push(`/${this.$i18n.locale}/dashboard/page=1`);
           })
           .catch(error => {
             this.errors = error.response.data.errors;
             console.log("Errors", error.response.data.errors);
           });
       }
     }
   };
</script>
<style lang="scss" scoped="">
   @import "@/assets/scss/components/_ArticleSection.scss";
</style>

The component provides the ability to conveniently create and edit articles.

– component PaginationMenu.vue, displaying convenient navigation through pagination pages:

<template>
 
 
<div class="Dashboard__pagination">
      {{ createIterator() }}
 
<ul v-if="backendData.pages > 1" class="pagination text-center">
 
 
 	<li v-if="backendData.current_page == 1" class="disabled">
            <span>{{ $t("First") }}</span>
</li>
 
 
 	<li v-else="">
            <router-link to="page=1">{{ $t("First") }}</router-link>
</li>
 
 
 	<li v-if="iterator !== 1" class="disabled"><span>...</span></li>
 
 
         <template v-for="i in backendData.pages">
            <template v-if="firstCheckPagination()">
 
 	<li v-if="iterator == backendData.current_page" class="active">
                  <span>{{ iterator }}</span>
</li>
 
 
 	<li v-else="">
                  <router-link :to="'page=' + iterator">{{ iterator }}</router-link>
</li>
 
 
 	<li v-if="secondCheckPagination()" class="disabled">
                  <span>...</span>
</li>
 
 
               {{ incrementIterator() }}
            </template>
         </template>
 
 
 	<li v-if="backendData.current_page == backendData.pages" class="disabled">
            <span>{{ $t("Last") }}</span>
</li>
 
 
 	<li v-else="">
            <router-link :to="'page=' + backendData.pages">{{
               $t("Last")
               }}
            </router-link>
</li>
 
</ul>
 
</div>
 
 
</template>
<script>
   export default {
     props: ["backendData"],
     data() {
       return {
         maxCount: 5,
         sideCount: 4
       };
     },
     methods: {
       createIterator() {
         this.iterator =
           Number(this.backendData.current_page) > this.maxCount
             ? Number(this.backendData.current_page) - this.sideCount
             : 1;
       },
       firstCheckPagination() {
         return (
           this.iterator <=
             Number(this.backendData.current_page) + this.sideCount &&
           this.iterator <= this.backendData.pages
         );
       },
       secondCheckPagination() {
         return (
           this.iterator ==
             Number(this.backendData.current_page) + this.sideCount &&
           this.iterator < this.backendData.pages
         );
       },
       incrementIterator() {
         this.iterator++;
       }
     }
   };
</script>
<style lang="scss" scoped="">
   @import "@/assets/scss/components/_PaginationMenu.scss";
</style>

– Article.vue component, which will be an article with the ability to delete and go to the editing page:

<template>
 
<div class="article">
 
<h2 class="title">{{ title }}</h2>
 
      <span class="subtitle">{{ subtitle }}</span>
      {{ content }}
      <router-link class="edit" :to="'/' + $i18n.locale + '/article/edit/' + id">{{ $t("Edit") }}</router-link>
      <button @click="deleteArticle" class="delete">{{ $t("Delete") }}</button>
</div>
 
</template>
<script>
   import axios from "axios";
 
   export default {
     props: ["title", "subtitle", "content", "id"],
     methods: {
       deleteArticle() {
         axios
           .delete(`${process.env.VUE_APP_BASE_URL}/api/articles/${this.id}`)
           .then(response => {
             console.log(response);
             this.$emit("delete");
             this.$router.push(`/${this.$i18n.locale}/dashboard/page=1`);
           })
           .catch(error => {
             console.log(error.response.data);
           });
       }
     }
   };
</script>
<style lang="scss" scoped="">
   @import "@/assets/scss/components/_Article.scss";
</style>

– component Dashboard.vue:

 

It will be a page that contains previously created components and displays all the articles of the project using pagination

– component EditPage.vue, which will be the article editing page:

<template>
 
 
<div class="dashboard">
      <header :pagename="pageName">
 
 
<div class="container">
            <articlesection @create="loadPageData(1)" :iscreatesection="true">
               <paginationmenu :backenddata="pageData">
                  <article @delete="loadPageData(1)" :id="article.id" :title="article.title" :subtitle="article.subtitle" :content="article.content" v-for="(article, index) in pageData.data" :key="index">
                  </article>
               </paginationmenu>
            </articlesection>
</div>
 
 
      </header>
</div>
 
 
</template>
<script>
   import ArticleSection from "@/components/ArticleSection";
   import Header from "@/components/Header";
   import Article from "@/components/Article";
   import PaginationMenu from "@/components/PaginationMenu";
   import axios from "axios";
 
   export default {
     data() {
       return {
         pageData: []
       };
     },
     computed: {
       pageName() {
         return this.$t("Dashboard");
       }
     },
     components: {
       Header,
       Article,
       PaginationMenu,
       ArticleSection
     },
     mounted() {
       this.loadPageData(this.$route.params.num);
     },
     watch: {
       $route(to, from) {
         this.loadPageData(this.$route.params.num);
       }
     },
     methods: {
       checkPaginationRoute() {
         if (
           (this.pageData.pages !== 0 &&
             this.$route.params.num > this.pageData.pages) ||
           isNaN(Number(this.$route.params.num))
         ) {
           this.$router.push(`/${this.$i18n.locale}/error`);
         }
       },
       loadPageData(pageNum) {
         axios
           .get(
             `${process.env.VUE_APP_BASE_URL}/api/articles/{article}?page=${pageNum}`
           )
           .then(response => {
             this.pageData = response.data;
             this.pageData.pages = Math.ceil(
               this.pageData.total / this.pageData.per_page
             );
             console.log("Page data", this.pageData);
             this.checkPaginationRoute();
           })
           .catch(error => {
             console.log(error.response.data);
           });
       }
     }
   };
</script>
<style lang="scss" scoped="">
   @import "@/assets/scss/components/_Dashboard.scss";
</style>

Component styles are in separate files and are connected inside the components. Styles and other media resources can be viewed in the project repository: [LiveJournal](https://github.com/genyaevgeney/work-event-project)

You also need to create an .env file inside the root of the frontend and add the VUE_APP_BASE_URL variable there which will contain the site URL

Now you need to configure the project to work with the created frontend, build the frontend and delete all the extra files. After that, the project will be fully operational.

It remains to make tests to test the controller. To do this, you need to configure the database connection for the tests in the phpunit.xml file. Then create the test with the command:

php artisan make:test ControllerTest

The test will test the functioning of the project controller:

namespace Tests\Feature;
 
use Tests\TestCase;
 
class ControllerTest extends TestCase
{
    /**
     * Test article controller functional
     *
     * @return void
     */
    public function testArticleController()
    {
        $isSuccessfulTest = true;
        $statusArr = [];
        $data = [
                'title' =&gt; 'testTitle',
                'subtitle' =&gt; 'testSubtitle',
                'content' =&gt; 'testContent'
            ];
        $responseCreate = $this-&gt;json('POST', '/api/articles', $data);
        $createStatus = $responseCreate-&gt;getStatusCode();
        array_push($statusArr, $createStatus);
        $responsePaginate = $this-&gt;json('GET', '/api/articles/{article}?page=1');
        $paginateStatus = $responsePaginate-&gt;getStatusCode();
        array_push($statusArr, $paginateStatus);
        $articleId = $responsePaginate-&gt;decodeResponseJson()['data'][0]['id'];
        $responseGetById = $this-&gt;json('GET', "/api/articles/$articleId/edit");
        $getByIdStatus = $responseGetById-&gt;getStatusCode();
        array_push($statusArr, $getByIdStatus);
        $updateData = [
                'title' =&gt; 'updateTestTitle',
                'subtitle' =&gt; 'updateTestSubtitle',
                'content' =&gt; 'updateTestContent',
                '_method' =&gt; 'put'
            ];
        $responseUpdate = $this-&gt;json('POST', "/api/articles/$articleId", $updateData);
        $updateStatus = $responseUpdate-&gt;getStatusCode();
        array_push($statusArr, $updateStatus);
        $responseDelete = $this-&gt;json('DELETE', "/api/articles/$articleId");
        $deleteStatus = $responseDelete-&gt;getStatusCode();
        array_push($statusArr, $deleteStatus);
        foreach ($statusArr as $value) {
            if ($value != 200) {
                $isSuccessfulTest = false;
                break;
            }
        }
        $this-&gt;assertTrue($isSuccessfulTest);
    }
}

At this stage, the project is completely ready. During this time, a web application was created to post and manage articles on the site using the capabilities of laravel and vue.js.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.