{"id":3395,"date":"2019-01-21T03:00:35","date_gmt":"2019-01-21T03:00:35","guid":{"rendered":"http:\/\/myprojects.advchaweb.com\/?p=3395"},"modified":"2019-01-31T03:33:35","modified_gmt":"2019-01-31T03:33:35","slug":"create-laravel-cms","status":"publish","type":"post","link":"https:\/\/myprojects.advchaweb.com\/index.php\/2019\/01\/21\/create-laravel-cms\/","title":{"rendered":"Create Laravel CMS"},"content":{"rendered":"<p>Ref: https:\/\/pusher.com\/tutorials\/cms-laravel-vue-part-1<\/p>\n<p>Install Laravel Installer globally (It&#8217;d be better than &#8216;create project&#8217; and download from the laravel source):<\/p>\n<pre class=\"lang:default decode:true\">teddy@teddy:~\/Documents\/works\/laravel$ composer global require \"laravel\/installer\"\r\nChanged current directory to \/home\/teddy\/.composer\r\n.\/composer.json is not writable.\r\nteddy@teddy:~\/Documents\/works\/laravel$ sudo composer global require \"laravel\/installer\"\r\n[sudo] password for teddy: \r\nChanged current directory to \/home\/teddy\/.composer\r\nDo not run Composer as root\/super user! See https:\/\/getcomposer.org\/root for details\r\nUsing version ^2.0 for laravel\/installer\r\n.\/composer.json has been updated\r\nLoading composer repositories with package information\r\nUpdating dependencies (including require-dev)\r\nNothing to install or update\r\nGenerating autoload files\r\nteddy@teddy:~\/Documents\/works\/laravel$ echo 'export PATH=\"$HOME\/.composer\/vendor\/bin:$PATH\"' &gt;&gt; ~\/.bashrc\r\nteddy@teddy:~\/Documents\/works\/laravel$ source ~\/.bashrc\r\nteddy@teddy:~\/Documents\/works\/laravel$ laravel\r\nLaravel Installer 2.0.1\r\n\r\nUsage:\r\n  command [options] [arguments]\r\n\r\nOptions:\r\n  -h, --help            Display this help message\r\n  -q, --quiet           Do not output any message\r\n  -V, --version         Display this application version\r\n      --ansi            Force ANSI output\r\n      --no-ansi         Disable ANSI output\r\n  -n, --no-interaction  Do not ask any interactive question\r\n  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug\r\n\r\nAvailable commands:\r\n  help  Displays help for a command\r\n  list  Lists commands\r\n  new   Create a new Laravel application.<\/pre>\n<p>Create the Laravel CMS Project &#8216;laravel-cms&#8217;:<\/p>\n<pre class=\"lang:default decode:true\">teddy@teddy:~\/Documents\/works\/laravel$ laravel new laravel-cms\r\nCrafting application...\r\nLoading composer repositories with package information\r\nInstalling dependencies (including require-dev) from lock file\r\nPackage operations: 86 installs, 0 updates, 0 removals\r\n  - Installing doctrine\/inflector (v1.3.0): Loading from cache\r\n  - Installing doctrine\/lexer (v1.0.1): Loading from cache\r\n  - Installing dragonmantank\/cron-expression (v2.2.0): Loading from cache\r\n  - Installing erusev\/parsedown (1.7.1): Loading from cache\r\n  - Installing vlucas\/phpdotenv (v2.5.2): Downloading (100%)         \r\n  - Installing symfony\/css-selector (v4.2.2): Downloading (100%)         \r\n  - Installing tijsverkoyen\/css-to-inline-styles (2.2.1): Loading from cache\r\n  - Installing symfony\/polyfill-php72 (v1.10.0): Downloading (100%)         \r\n  - Installing symfony\/polyfill-mbstring (v1.10.0): Loading from cache\r\n  - Installing symfony\/var-dumper (v4.2.2): Downloading (100%)         \r\n  - Installing symfony\/routing (v4.2.2): Downloading (100%)         \r\n  - Installing symfony\/process (v4.2.2): Downloading (100%)         \r\n  - Installing symfony\/polyfill-ctype (v1.10.0): Loading from cache\r\n  - Installing symfony\/http-foundation (v4.2.2): Downloading (100%)         \r\n  - Installing symfony\/contracts (v1.0.2): Downloading (100%)         \r\n  - Installing symfony\/event-dispatcher (v4.2.2): Downloading (100%)         \r\n  - Installing psr\/log (1.1.0): Loading from cache\r\n  - Installing symfony\/debug (v4.2.2): Downloading (100%)         \r\n  - Installing symfony\/http-kernel (v4.2.2): Downloading (100%)         \r\n  - Installing symfony\/finder (v4.2.2): Downloading (100%)         \r\n  - Installing symfony\/console (v4.2.2): Downloading (100%)         \r\n  - Installing egulias\/email-validator (2.1.7): Downloading (100%)         \r\n  - Installing swiftmailer\/swiftmailer (v6.1.3): Loading from cache\r\n  - Installing paragonie\/random_compat (v9.99.99): Loading from cache\r\n  - Installing ramsey\/uuid (3.8.0): Loading from cache\r\n  - Installing psr\/simple-cache (1.0.1): Loading from cache\r\n  - Installing psr\/container (1.0.0): Loading from cache\r\n  - Installing opis\/closure (3.1.5): Downloading (100%)         \r\n  - Installing symfony\/translation (v4.2.2): Downloading (100%)         \r\n  - Installing nesbot\/carbon (1.36.2): Loading from cache\r\n  - Installing monolog\/monolog (1.24.0): Loading from cache\r\n  - Installing league\/flysystem (1.0.49): Loading from cache\r\n  - Installing ralouphie\/getallheaders (2.0.5): Loading from cache\r\n  - Installing psr\/http-message (1.0.1): Loading from cache\r\n  - Installing guzzlehttp\/psr7 (1.5.2): Loading from cache\r\n  - Installing guzzlehttp\/promises (v1.3.1): Loading from cache\r\n  - Installing guzzlehttp\/guzzle (6.3.3): Loading from cache\r\n  - Installing laravel\/slack-notification-channel (v1.0.3): Downloading (connectDownloading (100%)         \r\n  - Installing zendframework\/zend-diactoros (1.8.6):Downloading (100%)         )\r\n  - Installing php-http\/promise (v1.0.0): Downloading (100%)         \r\n  - Installing php-http\/httplug (v1.1.0): Downloading (100%)         \r\n  - Installing php-http\/guzzle6-adapter (v1.1.1): Downloading (100%)         \r\n  - Installing lcobucci\/jwt (3.2.5): Downloading (100%)         \r\n  - Installing nexmo\/client (1.6.0): Downloading (100%)         \r\n  - Installing laravel\/nexmo-notification-channel (v1.0.1): Downloading (connectDownloading (100%)         \r\n  - Installing laravel\/framework (v5.7.21): Downloading (100%)         \r\n  - Installing fideloper\/proxy (4.1.0): Downloading (100%)         \r\n  - Installing jakub-onderka\/php-console-color (v0.2): Loading from cache\r\n  - Installing nikic\/php-parser (v4.2.0): Downloading (100%)         \r\n  - Installing jakub-onderka\/php-console-highlighter (v0.4): Downloading (connecDownloading (100%)         \r\n  - Installing dnoegel\/php-xdg-base-dir (0.1): Loading from cache\r\n  - Installing psy\/psysh (v0.9.9): Downloading (100%)         \r\n  - Installing laravel\/tinker (v1.0.8): Downloading (100%)         \r\n  - Installing beyondcode\/laravel-dump-server (1.2.2): Downloading (connecting..Downloading (100%)         \r\n  - Installing fzaninotto\/faker (v1.8.0): Loading from cache\r\n  - Installing hamcrest\/hamcrest-php (v2.0.0): Loading from cache\r\n  - Installing mockery\/mockery (1.2.0): Loading from cache\r\n  - Installing filp\/whoops (2.3.1): Downloading (100%)         \r\n  - Installing nunomaduro\/collision (v2.1.1): Downloading (100%)         \r\n  - Installing webmozart\/assert (1.4.0): Loading from cache\r\n  - Installing phpdocumentor\/reflection-common (1.0.1): Loading from cache\r\n  - Installing phpdocumentor\/type-resolver (0.4.0): Loading from cache\r\n  - Installing phpdocumentor\/reflection-docblock (4.3.0): Loading from cache\r\n  - Installing phpunit\/php-token-stream (3.0.1): Downloading (100%)         \r\n  - Installing sebastian\/version (2.0.1): Loading from cache\r\n  - Installing sebastian\/resource-operations (2.0.1): Downloading (connecting...Downloading (100%)         \r\n  - Installing sebastian\/recursion-context (3.0.0): Loading from cache\r\n  - Installing sebastian\/object-reflector (1.1.1): Loading from cache\r\n  - Installing sebastian\/object-enumerator (3.0.3): Loading from cache\r\n  - Installing sebastian\/global-state (2.0.0): Loading from cache\r\n  - Installing sebastian\/exporter (3.1.0): Loading from cache\r\n  - Installing sebastian\/environment (4.0.1): Downloading (100%)         \r\n  - Installing sebastian\/diff (3.0.1): Loading from cache\r\n  - Installing sebastian\/comparator (3.0.2): Loading from cache\r\n  - Installing phpunit\/php-timer (2.0.0): Loading from cache\r\n  - Installing phpunit\/php-text-template (1.2.1): Loading from cache\r\n  - Installing phpunit\/php-file-iterator (2.0.2): Loading from cache\r\n  - Installing theseer\/tokenizer (1.1.0): Loading from cache\r\n  - Installing sebastian\/code-unit-reverse-lookup (1.0.1): Loading from cache\r\n  - Installing phpunit\/php-code-coverage (6.1.4): Downloading (100%)         \r\n  - Installing doctrine\/instantiator (1.1.0): Loading from cache\r\n  - Installing phpspec\/prophecy (1.8.0): Loading from cache\r\n  - Installing phar-io\/version (2.0.1): Loading from cache\r\n  - Installing phar-io\/manifest (1.0.3): Loading from cache\r\n  - Installing myclabs\/deep-copy (1.8.1): Loading from cache\r\n  - Installing phpunit\/phpunit (7.5.2): Downloading (100%)         \r\nsymfony\/routing suggests installing doctrine\/annotations (For using the annotation loader)\r\nsymfony\/routing suggests installing symfony\/config (For using the all-in-one router or any loader)\r\nsymfony\/routing suggests installing symfony\/dependency-injection (For loading routes from a service)\r\nsymfony\/routing suggests installing symfony\/expression-language (For using expression matching)\r\nsymfony\/routing suggests installing symfony\/yaml (For using the YAML loader)\r\nsymfony\/contracts suggests installing psr\/cache (When using the Cache contracts)\r\nsymfony\/contracts suggests installing symfony\/cache-contracts-implementation ()\r\nsymfony\/contracts suggests installing symfony\/service-contracts-implementation ()\r\nsymfony\/event-dispatcher suggests installing symfony\/dependency-injection ()\r\nsymfony\/http-kernel suggests installing symfony\/browser-kit ()\r\nsymfony\/http-kernel suggests installing symfony\/config ()\r\nsymfony\/http-kernel suggests installing symfony\/dependency-injection ()\r\nsymfony\/console suggests installing symfony\/lock ()\r\nswiftmailer\/swiftmailer suggests installing true\/punycode (Needed to support internationalized email addresses, if ext-intl is not installed)\r\nparagonie\/random_compat suggests installing ext-libsodium (Provides a modern crypto API that can be used to generate random bytes.)\r\nramsey\/uuid suggests installing ext-libsodium (Provides the PECL libsodium extension for use with the SodiumRandomGenerator)\r\nramsey\/uuid suggests installing ext-uuid (Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator)\r\nramsey\/uuid suggests installing ircmaxell\/random-lib (Provides RandomLib for use with the RandomLibAdapter)\r\nramsey\/uuid suggests installing moontoast\/math (Provides support for converting UUID to 128-bit integer (in string form).)\r\nramsey\/uuid suggests installing ramsey\/uuid-console (A console application for generating UUIDs with ramsey\/uuid)\r\nramsey\/uuid suggests installing ramsey\/uuid-doctrine (Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type.)\r\nsymfony\/translation suggests installing symfony\/config ()\r\nsymfony\/translation suggests installing symfony\/yaml ()\r\nnesbot\/carbon suggests installing friendsofphp\/php-cs-fixer (Needed for the `composer phpcs` command. Allow to automatically fix code style.)\r\nnesbot\/carbon suggests installing phpstan\/phpstan (Needed for the `composer phpstan` command. Allow to detect potential errors.)\r\nmonolog\/monolog suggests installing aws\/aws-sdk-php (Allow sending log messages to AWS services like DynamoDB)\r\nmonolog\/monolog suggests installing doctrine\/couchdb (Allow sending log messages to a CouchDB server)\r\nmonolog\/monolog suggests installing ext-amqp (Allow sending log messages to an AMQP server (1.0+ required))\r\nmonolog\/monolog suggests installing ext-mongo (Allow sending log messages to a MongoDB server)\r\nmonolog\/monolog suggests installing graylog2\/gelf-php (Allow sending log messages to a GrayLog2 server)\r\nmonolog\/monolog suggests installing mongodb\/mongodb (Allow sending log messages to a MongoDB server via PHP Driver)\r\nmonolog\/monolog suggests installing php-amqplib\/php-amqplib (Allow sending log messages to an AMQP server using php-amqplib)\r\nmonolog\/monolog suggests installing php-console\/php-console (Allow sending log messages to Google Chrome)\r\nmonolog\/monolog suggests installing rollbar\/rollbar (Allow sending log messages to Rollbar)\r\nmonolog\/monolog suggests installing ruflin\/elastica (Allow sending log messages to an Elastic Search server)\r\nmonolog\/monolog suggests installing sentry\/sentry (Allow sending log messages to a Sentry server)\r\nleague\/flysystem suggests installing league\/flysystem-aws-s3-v2 (Allows you to use S3 storage with AWS SDK v2)\r\nleague\/flysystem suggests installing league\/flysystem-aws-s3-v3 (Allows you to use S3 storage with AWS SDK v3)\r\nleague\/flysystem suggests installing league\/flysystem-azure (Allows you to use Windows Azure Blob storage)\r\nleague\/flysystem suggests installing league\/flysystem-cached-adapter (Flysystem adapter decorator for metadata caching)\r\nleague\/flysystem suggests installing league\/flysystem-eventable-filesystem (Allows you to use EventableFilesystem)\r\nleague\/flysystem suggests installing league\/flysystem-rackspace (Allows you to use Rackspace Cloud Files)\r\nleague\/flysystem suggests installing league\/flysystem-sftp (Allows you to use SFTP server storage via phpseclib)\r\nleague\/flysystem suggests installing league\/flysystem-webdav (Allows you to use WebDAV storage)\r\nleague\/flysystem suggests installing league\/flysystem-ziparchive (Allows you to use ZipArchive adapter)\r\nleague\/flysystem suggests installing spatie\/flysystem-dropbox (Allows you to use Dropbox storage)\r\nleague\/flysystem suggests installing srmklive\/flysystem-dropbox-v2 (Allows you to use Dropbox storage for PHP 5 applications)\r\nlcobucci\/jwt suggests installing mdanter\/ecc (Required to use Elliptic Curves based algorithms.)\r\nlaravel\/framework suggests installing aws\/aws-sdk-php (Required to use the SQS queue driver and SES mail driver (^3.0).)\r\nlaravel\/framework suggests installing doctrine\/dbal (Required to rename columns and drop SQLite columns (^2.6).)\r\nlaravel\/framework suggests installing league\/flysystem-aws-s3-v3 (Required to use the Flysystem S3 driver (^1.0).)\r\nlaravel\/framework suggests installing league\/flysystem-cached-adapter (Required to use the Flysystem cache (^1.0).)\r\nlaravel\/framework suggests installing league\/flysystem-rackspace (Required to use the Flysystem Rackspace driver (^1.0).)\r\nlaravel\/framework suggests installing league\/flysystem-sftp (Required to use the Flysystem SFTP driver (^1.0).)\r\nlaravel\/framework suggests installing moontoast\/math (Required to use ordered UUIDs (^1.1).)\r\nlaravel\/framework suggests installing pda\/pheanstalk (Required to use the beanstalk queue driver (^3.0).)\r\nlaravel\/framework suggests installing predis\/predis (Required to use the redis cache and queue drivers (^1.0).)\r\nlaravel\/framework suggests installing pusher\/pusher-php-server (Required to use the Pusher broadcast driver (^3.0).)\r\nlaravel\/framework suggests installing symfony\/dom-crawler (Required to use most of the crawler integration testing tools (^4.1).)\r\nlaravel\/framework suggests installing symfony\/psr-http-message-bridge (Required to psr7 bridging features (^1.0).)\r\npsy\/psysh suggests installing ext-pdo-sqlite (The doc command requires SQLite to work.)\r\npsy\/psysh suggests installing hoa\/console (A pure PHP readline implementation. You'll want this if your PHP install doesn't already support readline or libedit.)\r\nfilp\/whoops suggests installing whoops\/soap (Formats errors as SOAP responses)\r\nsebastian\/global-state suggests installing ext-uopz (*)\r\nphpunit\/phpunit suggests installing ext-soap (*)\r\nphpunit\/phpunit suggests installing phpunit\/php-invoker (^2.0)\r\nGenerating optimized autoload files\r\n&gt; @php -r \"file_exists('.env') || copy('.env.example', '.env');\"\r\n&gt; @php artisan key:generate --ansi\r\nApplication key set successfully.\r\n&gt; Illuminate\\Foundation\\ComposerScripts::postAutoloadDump\r\n&gt; @php artisan package:discover --ansi\r\nDiscovered Package: beyondcode\/laravel-dump-server\r\nDiscovered Package: fideloper\/proxy\r\nDiscovered Package: laravel\/nexmo-notification-channel\r\nDiscovered Package: laravel\/slack-notification-channel\r\nDiscovered Package: laravel\/tinker\r\nDiscovered Package: nesbot\/carbon\r\nDiscovered Package: nunomaduro\/collision\r\nPackage manifest generated successfully.\r\nApplication ready! Build something amazing.<\/pre>\n<p>Go to the project directory and check the laravel version:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel$ cd laravel-cms\/\r\nteddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan help -V\r\nLaravel Framework 5.7.21<\/pre>\n<p>Run it for the first time:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan serve<\/pre>\n<p>Open it on your browser : http:\/\/localhost:8000\/<a href=\"http:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/Laravel-5.7.21.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-3400\" src=\"http:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/Laravel-5.7.21-1024x478.jpg\" alt=\"\" width=\"840\" height=\"392\" srcset=\"https:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/Laravel-5.7.21-1024x478.jpg 1024w, https:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/Laravel-5.7.21-300x140.jpg 300w, https:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/Laravel-5.7.21-768x359.jpg 768w, https:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/Laravel-5.7.21-1200x560.jpg 1200w, https:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/Laravel-5.7.21.jpg 1351w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/a>Set up the database (MySql)<br \/>\nCreate a new database &#8216;laravel_cms&#8217;<br \/>\nthen modify .env file to match your mysql server setting and database<\/p>\n<p>SETTING UP USER ROLES<br \/>\nLike most content management systems, we are going to have a user role system so that our blog can have multiple types of users; the admin and regular user. The admin should be able to create a post and perform other CRUD operations on a post. The regular user, on the other hand, should be able to view and comment on a post.<\/p>\n<p>For us to implement this functionality, we need to implement user authentication and add a simple role authorization system.<\/p>\n<p>SETTING UP USER AUTHENTICATION<br \/>\nLaravel provides user authentication out of the box, which is great, and we can key into the feature by running a single command:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan make:auth\r\nAuthentication scaffolding generated successfully.<\/pre>\n<p>The above will create all that\u2019s necessary for authentication in our application so we do not need to do anything extra.<\/p>\n<p>SETTING UP ROLE AUTHORIZATION<br \/>\nWe need a model for the user roles so let\u2019s create one and an associated migration file:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan make:model Role -m\r\nModel created successfully.\r\nCreated Migration: 2019_01_21_031440_create_roles_table<\/pre>\n<p>In the\u00a0database\/migrations\u00a0folder, find the newly created migration file and update the\u00a0CreateRolesTable class (2019_01_21_031440_create_roles_table.php) with this snippet:<br \/>\nUpdate with adding two new fields: $table-&gt;string(&#8216;name&#8217;) and<br \/>\n$table-&gt;string(&#8216;description&#8217;):<\/p>\n<pre class=\"lang:default decode:true\">\/\/laravel-cms\/database\/migrations\/2019_01_21_031440_create_roles_table.php\r\n\r\nuse Illuminate\\Support\\Facades\\Schema;\r\nuse Illuminate\\Database\\Schema\\Blueprint;\r\nuse Illuminate\\Database\\Migrations\\Migration;\r\n\r\nclass CreateRolesTable extends Migration\r\n{\r\n    \/**\r\n     * Run the migrations.\r\n     *\r\n     * @return void\r\n     *\/\r\n    public function up()\r\n    {\r\n        Schema::create('roles', function (Blueprint $table) {\r\n            $table-&gt;increments('id');\r\n            $table-&gt;string('name');\r\n            $table-&gt;string('description');\r\n            $table-&gt;timestamps();\r\n        });\r\n    }\r\n\r\n    \/**\r\n     * Reverse the migrations.\r\n     *\r\n     * @return void\r\n     *\/\r\n    public function down()\r\n    {\r\n        Schema::dropIfExists('roles');\r\n    }\r\n}<\/pre>\n<p>We intend to create a many-to-many relationship between the\u00a0User\u00a0and\u00a0Role\u00a0models so let\u2019s add a relationship method on both models.<\/p>\n<p>Open the\u00a0User\u00a0model and add the following method:<\/p>\n<pre class=\"lang:default decode:true\">\/\/laravel\/laravel-cms\/app\/User.php\r\npublic function roles(){\r\n    return $this-&gt;belongsToMany(Role::class);\r\n}<\/pre>\n<p>Open the\u00a0Role\u00a0model and include the following method:<\/p>\n<pre class=\"lang:default decode:true \">\/\/laravel-cms\/app\/Role.php\r\npublic function users(){\r\n    return $this-&gt;belongsToMany(User::class);\r\n}<\/pre>\n<p>We are also going to need a pivot table to associate each user with a matching role so let\u2019s create a new migration file for the\u00a0<strong>role_user<\/strong>\u00a0table:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan make:migration create_role_user_table\r\nCreated Migration: 2019_01_21_032646_create_role_user_table<\/pre>\n<p>In the\u00a0database\/migrations\u00a0folder, find the newly created migration file and update the\u00a0CreateRoleUserTable class with this snippet:<\/p>\n<pre class=\"lang:default decode:true\">    \/\/laravel-cms\/database\/migrations\/2019_01_21_032646_create_role_user_table.php\r\n    public function up()\r\n    {\r\n        Schema::create('role_user', function (Blueprint $table) {\r\n            $table-&gt;increments('id');\r\n            $table-&gt;integer('role_id')-&gt;unsigned();\r\n            $table-&gt;integer('user_id')-&gt;unsigned();\r\n            \/\/$table-&gt;timestamps();\r\n        });\r\n    }<\/pre>\n<p>Next, let\u2019s create seeders that will populate the\u00a0users\u00a0and\u00a0roles\u00a0tables with some data. In your terminal, run the following command to create the database seeders:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan make:seeder RoleTableSeeder\r\nSeeder created successfully.\r\nteddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan make:seeder UserTableSeeder\r\nSeeder created successfully.<\/pre>\n<p>In the\u00a0database\/seeds\u00a0folder, open the\u00a0RoleTableSeeder.php\u00a0file and replace the contents with the following code:<\/p>\n<pre class=\"lang:default decode:true \">\/\/laravel-cms\/database\/seeds\/RoleTableSeeder.php\r\nuse Illuminate\\Database\\Seeder;\r\nuse App\\Role;\r\n\r\nclass RoleTableSeeder extends Seeder\r\n{\r\n    \/**\r\n     * Run the database seeds.\r\n     *\r\n     * @return void\r\n     *\/\r\n    public function run()\r\n    {\r\n        $role_regular_user = new Role;\r\n        $role_regular_user-&gt;name = 'user';\r\n        $role_regular_user-&gt;description = 'A regular user';\r\n        $role_regular_user-&gt;save();    \r\n\r\n        $role_admin_user = new Role;\r\n        $role_admin_user-&gt;name = 'admin';\r\n        $role_admin_user-&gt;description = 'An admin user';\r\n        $role_admin_user-&gt;save();    \r\n    }\r\n}<\/pre>\n<p>Open the\u00a0UserTableSeeder.php\u00a0file and replace the contents with the following code:<\/p>\n<pre class=\"lang:default decode:true \">\/\/laravel-cms\/database\/seeds\/UserTableSeeder.php\r\nuse Illuminate\\Database\\Seeder;\r\nuse Illuminate\\Support\\Facades\\Hash;\r\nuse App\\User;\r\nuse App\\Role;\r\n\r\nclass UserTableSeeder extends Seeder\r\n{\r\n    \/**\r\n     * Run the database seeds.\r\n     *\r\n     * @return void\r\n     *\/\r\n    public function run()\r\n    {\r\n        $user = new User;\r\n        $user-&gt;name = 'Satria Faestha';\r\n        $user-&gt;email = 'satriaf@yahoo.com';\r\n        $user-&gt;password = bcrypt('teddy');\r\n        $user-&gt;save();\r\n        $user-&gt;roles()-&gt;attach(Role::where('name', 'user')-&gt;first());\r\n\r\n        $admin = new User;\r\n        $admin-&gt;name = 'Admin';\r\n        $admin-&gt;email = 'advcha@yahoo.com';\r\n        $admin-&gt;password = bcrypt('teddy');\r\n        $admin-&gt;save();\r\n        $admin-&gt;roles()-&gt;attach(Role::where('name', 'admin')-&gt;first());\r\n    }\r\n}<\/pre>\n<p>We also need to update the\u00a0DatabaseSeeder\u00a0class. Open the file and update the\u00a0run\u00a0method as seen below:<\/p>\n<pre class=\"lang:default decode:true\">    \/\/laravel-cms\/database\/seeds\/DatabaseSeeder.php\r\n    public function run()\r\n    {\r\n        $this-&gt;call([\r\n            RoleTableSeeder::class,\r\n            UserTableSeeder::class,\r\n        ]);\r\n    }<\/pre>\n<p>Next, let\u2019s update the\u00a0User\u00a0model. We will be adding a\u00a0checkRoles\u00a0method that checks what role a user has. We will return a 404 page where a user doesn\u2019t have the expected role for a page. Open the\u00a0User\u00a0model and add these methods:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/laravel-cms\/app\/User.php\r\n    public function checkRoles($roles){\r\n        if(!is_array($roles)){\r\n            $roles = [$roles];\r\n        }\r\n        \r\n        if(!$this-&gt;hasAnyRole($roles)){\r\n            auth()-&gt;logout();\r\n            abort(404);\r\n        }\r\n    }\r\n    \r\n    public function hasAnyRole($roles):bool {\r\n        return (bool) $this-&gt;roles()-&gt;whereIn('name', $roles)-&gt;first();\r\n    }\r\n    \r\n    public function hasRole($role):bool {\r\n        return (bool)$this-&gt;roles()-&gt;where('name', $role)-&gt;first();\r\n    }<\/pre>\n<p>Let\u2019s modify the\u00a0RegisterController.php\u00a0file in the\u00a0Controllers\/Auth\u00a0folder so that a default role, the user role, is always attached to a new user at registration.<\/p>\n<p>Open the\u00a0RegisterController\u00a0and update the\u00a0create\u00a0action with the following code:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/laravel-cms\/app\/Http\/Controllers\/Auth\/RegisterController.php \r\n    protected function create(array $data)\r\n    {\r\n        \/*return User::create([\r\n            'name' =&gt; $data['name'],\r\n            'email' =&gt; $data['email'],\r\n            'password' =&gt; Hash::make($data['password']),\r\n        ]);*\/\r\n        $user = User::create([\r\n            'name' =&gt; $data['name'],\r\n            'email' =&gt; $data['email'],\r\n            'password' =&gt; Hash::make($data['password']),\r\n        ]);\r\n        \r\n        $user-&gt;roles()-&gt;attach(\\App\\Role::where('name', 'user')-&gt;first());\r\n        \r\n        return $user;\r\n    }<\/pre>\n<p>Now let\u2019s migrate and seed the database so that we can log in with the sample accounts. To do this, run the following command in your terminal:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan migrate:fresh --seed\r\nDropped all tables successfully.\r\nMigration table created successfully.\r\nMigrating: 2014_10_12_000000_create_users_table\r\nMigrated:  2014_10_12_000000_create_users_table\r\nMigrating: 2014_10_12_100000_create_password_resets_table\r\nMigrated:  2014_10_12_100000_create_password_resets_table\r\nMigrating: 2019_01_21_031440_create_roles_table\r\n\r\n   Illuminate\\Database\\QueryException  : SQLSTATE[42S21]: Column already exists: 1060 Duplicate column name 'created_at' (SQL: create table `roles` (`created_at` timestamp null, `updated_at` timestamp null, `name` varchar(255) not null, `description` varchar(255) not null, `created_at` timestamp null, `updated_at` timestamp null) default character set utf8mb4 collate 'utf8mb4_unicode_ci')\r\n\r\n  at \/home\/teddy\/Documents\/works\/laravel\/laravel-cms\/vendor\/laravel\/framework\/src\/Illuminate\/Database\/Connection.php:664\r\n    660|         \/\/ If an exception occurs when attempting to run a query, we'll format the error\r\n    661|         \/\/ message to include the bindings with SQL, which will make this exception a\r\n    662|         \/\/ lot more helpful to the developer instead of just the database's errors.\r\n    663|         catch (Exception $e) {\r\n  &gt; 664|             throw new QueryException(\r\n    665|                 $query, $this-&gt;prepareBindings($bindings), $e\r\n    666|             );\r\n    667|         }\r\n    668| \r\n\r\n  Exception trace:\r\n\r\n  1   PDOException::(\"SQLSTATE[42S21]: Column already exists: 1060 Duplicate column name 'created_at'\")\r\n      \/home\/teddy\/Documents\/works\/laravel\/laravel-cms\/vendor\/laravel\/framework\/src\/Illuminate\/Database\/Connection.php:458\r\n\r\n  2   PDOStatement::execute()\r\n      \/home\/teddy\/Documents\/works\/laravel\/laravel-cms\/vendor\/laravel\/framework\/src\/Illuminate\/Database\/Connection.php:458\r\n\r\n  Please use the argument -v to see more details.<\/pre>\n<p>SOLUTION: check again &#8216;CreateRolesTable&#8217; Class in \/home\/teddy\/Documents\/works\/laravel\/laravel-cms\/database\/migrations\/2019_01_21_031440_create_roles_table.php. Somehow the &#8216;up&#8217; function is changed!!!<\/p>\n<pre class=\"lang:default decode:true \">Illuminate\\Database\\QueryException  : SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'role_id' cannot be null (SQL: insert into `role_user` (`role_id`, `user_id`) values (, 1))\r\n<\/pre>\n<p>SOLUTION: It&#8217;s related with the above problem!<br \/>\nThis is working:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan migrate:fresh --seed\r\nDropped all tables successfully.\r\nMigration table created successfully.\r\nMigrating: 2014_10_12_000000_create_users_table\r\nMigrated:  2014_10_12_000000_create_users_table\r\nMigrating: 2014_10_12_100000_create_password_resets_table\r\nMigrated:  2014_10_12_100000_create_password_resets_table\r\nMigrating: 2019_01_21_031440_create_roles_table\r\nMigrated:  2019_01_21_031440_create_roles_table\r\nMigrating: 2019_01_21_032646_create_role_user_table\r\nMigrated:  2019_01_21_032646_create_role_user_table\r\nSeeding: RoleTableSeeder\r\nSeeding: UserTableSeeder\r\nDatabase seeding completed successfully.<\/pre>\n<p>In order to test that our roles work as they should, we will make an update to the\u00a0HomeController.php\u00a0file. Open the\u00a0HomeController\u00a0and update the\u00a0index\u00a0method as seen below:<\/p>\n<pre class=\"lang:default decode:true\">    \/\/laravel-cms\/app\/Http\/Controllers\/HomeController.php\r\n    public function index(Request $request)\r\n    {\r\n        $request-&gt;user()-&gt;checkRoles('admin');\r\n        \r\n        return view('home');\r\n    }<\/pre>\n<p>Now, only administrators should be able to see the dashboard. In a more complex application, we would use a\u00a0middleware\u00a0to do this instead.<br \/>\nWe can test that this works by serving the application and logging (http:\/\/localhost:8000\/login) in both user accounts: satriaf@yahoo.com (user) and advcha@yahoo.com (admin).<\/p>\n<p>Remember that in our\u00a0UserTableSeeder.php\u00a0file, we defined satriaf@yahoo.com as a regular user and advcha@yahoo.com as an admin, so satriaf@yahoo.com should see a 404 error after logging in and advcha@yahoo.com should be able to see the homepage. Also http:\/\/localhost:8000\/home would be redirected to http:\/\/localhost:8000\/login if none logged in!<\/p>\n<p>We will also confirm that whenever a new user registers, he is assigned a role and it is the role of a regular user. We will create a new user and call him user1@yahoo.com (password: user1_123) , he should see a 404 error right after:<\/p>\n<p>It works just as we wanted it to, however, it doesn\u2019t really make any sense for us to redirect a regular user to a 404 page. Instead, we will edit the\u00a0HomeController\u00a0so that it redirects users based on their roles, that is, it redirects a regular user to a regular homepage and an admin to an admin dashboard.<\/p>\n<p>Open the\u00a0HomeController.php\u00a0file and update the\u00a0index\u00a0method as seen below:<\/p>\n<pre class=\"lang:default decode:true\">    \/\/laravel-cms\/app\/Http\/Controllers\/HomeController.php\r\n    public function index(Request $request)\r\n    {\r\n        \/\/$request-&gt;user()-&gt;checkRoles('admin');\r\n        if($request-&gt;user()-&gt;hasRole('user')){\r\n            return redirect('\/');\r\n        }\r\n\r\n        if($request-&gt;user()-&gt;hasRole('admin')){\r\n            return redirect('\/admin\/dashboard');\r\n        }\r\n        \r\n        \/\/return view('home');\r\n    }<\/pre>\n<p>After we login as an user, we missed the logout link here. So modify the main template welcome.blade.php to add the logout link:<\/p>\n<pre class=\"lang:default decode:true\">\/\/laravel-cms\/resources\/views\/welcome.blade.php\r\n@if (Route::has('login'))\r\n    &lt;div class=\"top-right links\"&gt;\r\n        @auth\r\n            &lt;a href=\"{{ url('\/home') }}\"&gt;Home&lt;\/a&gt;\r\n            &lt;a href=\"{{ url('\/logout') }}\"&gt;Logout&lt;\/a&gt;\r\n        @else\r\n            &lt;a href=\"{{ route('login') }}\"&gt;Login&lt;\/a&gt;\r\n\r\n            @if (Route::has('register'))\r\n                &lt;a href=\"{{ route('register') }}\"&gt;Register&lt;\/a&gt;\r\n            @endif\r\n        @endauth\r\n    &lt;\/div&gt;\r\n@endif\r\n<\/pre>\n<p>But if we click the logout link, it&#8217;d show an error:<\/p>\n<pre class=\"lang:default decode:true\">Symfony \\ Component \\ HttpKernel \\ Exception \\ MethodNotAllowedHttpException\r\nNo message<\/pre>\n<p>It means we need to add a route for &#8216;logout&#8217;. Add the route in web.php:<\/p>\n<pre class=\"lang:default decode:true \">\/\/laravel\/laravel-cms\/routes\/web.php\r\nRoute::get('\/logout', 'Auth\\LoginController@logout')-&gt;name('logout' );<\/pre>\n<p>Ref: https:\/\/stackoverflow.com\/questions\/44716379\/laravel-5-4-24-throws-methodnotallowedhttpexception-during-logout-of-users<\/p>\n<p>If we serve our application and try to log in using the admin account, we will hit a 404 error because we do not have a controller or a view for the\u00a0admin\/dashboard\u00a0route. Let\u2019s create the\u00a0AdminController\u00a0with this command:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan make:controller AdminController\r\nController created successfully.<\/pre>\n<p>We will add the\u00a0\/admin\/\u00a0route to our\u00a0routes\/web.php\u00a0file:<\/p>\n<pre class=\"lang:default decode:true\">\/\/laravel-cms\/routes\/web.php\r\nRoute::get('\/admin\/{any}', 'AdminController@index')-&gt;where('any', '.*');<\/pre>\n<p>Note that we wrote\u00a0\/admin\/{any}\u00a0here because we intend to serve every page of the admin dashboard using the Vue router. When we start building the admin dashboard in the next article, we will let Vue handle all the routes of the\u00a0\/admin\u00a0pages.<\/p>\n<p>Let\u2019s update the\u00a0AdminController.php\u00a0file to use the auth middleware and include an\u00a0index()\u00a0action method:<\/p>\n<pre class=\"lang:default decode:true \">\/\/laravel-cms\/app\/Http\/Controllers\/AdminController.php\r\nclass AdminController extends Controller\r\n{\r\n    public function __construct(){\r\n        $this-&gt;middleware('auth');\r\n    }\r\n    \r\n    public function index(){\r\n        if(request()-&gt;user()-&gt;hasRole('admin')){\r\n            return view('admin.dashboard');\r\n        }\r\n\r\n        if(request()-&gt;user()-&gt;hasRole('user')){\r\n            return redirect('\/');\r\n        }\r\n    }\r\n}<\/pre>\n<p>In the\u00a0index() action method, we included a snippet that will ensure that only admin users can visit the admin dashboard and perform CRUD operations on posts. If we try to login as an admin, it&#8217;d show this error:<\/p>\n<pre class=\"lang:default decode:true \">InvalidArgumentException\r\nView [admin.dashboard] not found.<\/pre>\n<p>Because we haven&#8217;t create any view for the admin route. We&#8217;ll create it with vue router later.<\/p>\n<p>Here, we will create the\u00a0Post\u00a0model and start building the frontend for the application.<\/p>\n<p>Our application allows different levels of accessibility for two kinds of users; the regular user and admin. Now, we will focus on building the view that the regular users are permitted to see.<\/p>\n<p>Before we build any views, let\u2019s create the\u00a0Post\u00a0model as it is imperative to rendering the view. We will create the\u00a0Post\u00a0model with an associated resource controller and a migration file using this command:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan make:model Post -mr\r\nModel created successfully.\r\nCreated Migration: 2019_01_21_083457_create_posts_table\r\nController created successfully.\r\n<\/pre>\n<p>We added the\u00a0r\u00a0flag because we want the controller to be a resource controller. The\u00a0m\u00a0flag will generate a migration for the model.<\/p>\n<p>Let\u2019s navigate into the\u00a0database\/migrations\u00a0folder and update the\u00a0CreatePostsTable\u00a0class that was generated for us:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/laravel-cms\/database\/migrations\/2019_01_21_083457_create_posts_table.php\r\n    public function up()\r\n    {\r\n        Schema::create('posts', function (Blueprint $table) {\r\n            $table-&gt;increments('id');\r\n            $table-&gt;integer('user_id')-&gt;unsigned();\r\n            $table-&gt;string('title');\r\n            $table-&gt;text('body');\r\n            $table-&gt;binary('image')-&gt;nullable();\r\n            $table-&gt;timestamps();\r\n        });\r\n    }<\/pre>\n<p>We included a\u00a0user_id\u00a0property because we want to create a relationship between the\u00a0User\u00a0and\u00a0Post\u00a0models. A\u00a0Post\u00a0also has an\u00a0image\u00a0field, which is where its associated image\u2019s address will be stored.<\/p>\n<p>CREATING A DATABASE SEEDER FOR THE POST TABLE<br \/>\nWe will create a new seeder file for the\u00a0posts\u00a0table using this command:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan make:seeder PostTableSeeder\r\nSeeder created successfully.<\/pre>\n<p>Let\u2019s navigate into the\u00a0database\/seeds\u00a0folder and update the\u00a0PostTableSeeder.php\u00a0file:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/laravel-cms\/database\/seeds\/PostTableSeeder.php\r\n    public function run()\r\n    {\r\n        $post = new Post;\r\n        $post-&gt;user_id = 2;\r\n        $post-&gt;title = \"Using Laravel Seeders\";\r\n        $post-&gt;body = \"Laravel includes a simple method of seeding your database with test data using seed classes. All seed classes are stored in the database\/seeds directory. Seed classes may have any name you wish, but probably should follow some sensible convention, such as UsersTableSeeder, etc. By default, a DatabaseSeeder class is defined for you. From this class, you may use the  call method to run other seed classes, allowing you to control the seeding order.\";\r\n        $post-&gt;save();\r\n\r\n        $post = new Post;\r\n        $post-&gt;user_id = 2;\r\n        $post-&gt;title = \"Database: Migrations\";\r\n        $post-&gt;body = \"Migrations are like version control for your database, allowing your team to easily modify and share the application's database schema. Migrations are typically paired with Laravel's schema builder to easily build your application's database schema. If you have ever had to tell a teammate to manually add a column to their local database schema, you've faced the problem that database migrations solve.\";\r\n        $post-&gt;save();\r\n    }<\/pre>\n<p>Let\u2019s open the\u00a0DatabaseSeeder\u00a0and update it with the following code:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/laravel-cms\/database\/seeds\/DatabaseSeeder.php\r\n    public function run()\r\n    {\r\n        $this-&gt;call([\r\n            RoleTableSeeder::class,\r\n            UserTableSeeder::class,\r\n            PostTableSeeder::class,\r\n        ]);\r\n    }<\/pre>\n<p>When we run this seeder, it will create two new posts and assign both of them to the admin user whose ID is 2. We are attaching both posts to the admin user because the regular users are only allowed to view posts and make comments; they can\u2019t create a post. We will use this command to migrate our tables and seed the database:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan migrate:fresh --seed\r\nDropped all tables successfully.\r\nMigration table created successfully.\r\nMigrating: 2014_10_12_000000_create_users_table\r\nMigrated:  2014_10_12_000000_create_users_table\r\nMigrating: 2014_10_12_100000_create_password_resets_table\r\nMigrated:  2014_10_12_100000_create_password_resets_table\r\nMigrating: 2019_01_21_031440_create_roles_table\r\nMigrated:  2019_01_21_031440_create_roles_table\r\nMigrating: 2019_01_21_032646_create_role_user_table\r\nMigrated:  2019_01_21_032646_create_role_user_table\r\nMigrating: 2019_01_21_083457_create_posts_table\r\nMigrated:  2019_01_21_083457_create_posts_table\r\nSeeding: RoleTableSeeder\r\nSeeding: UserTableSeeder\r\nSeeding: PostTableSeeder\r\nDatabase seeding completed successfully.<\/pre>\n<p>DEFINING THE RELATIONSHIPS<br \/>\nJust as we previously created a many-to-many relationship between the\u00a0User\u00a0and\u00a0Role\u00a0models, we need to create a different kind of relationship between the\u00a0Post\u00a0and\u00a0User\u00a0models.<\/p>\n<p>We will define the relationship as a one-to-many relationship because a user will have many posts but a post will only ever belong to one user.<\/p>\n<p>Open the\u00a0User\u00a0model and include the method below:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/ File: .\/app\/User.php\r\n    public function posts()\r\n    {\r\n        return $this-&gt;hasMany(Post::class);\r\n    }<\/pre>\n<p>Open the\u00a0Post\u00a0model and include the method below:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/ File: .\/app\/Post.php\r\n    public function user()\r\n    {\r\n        return $this-&gt;belongsTo(User::class);\r\n    }<\/pre>\n<p>SETTING UP THE ROUTES<br \/>\nAt this point in our application, we do not have a front page with all the posts listed. Let\u2019s create so anyone can see all of the created posts. Asides from the front page, we also need a single post page in case a user needs to read a specific post.<\/p>\n<p>Let\u2019s include two new routes to our\u00a0routes\/web.php\u00a0file:<\/p>\n<p>The first route will match requests to the root of our application and will be handled by the\u00a0PostController@all\u00a0action:<\/p>\n<pre class=\"lang:default decode:true \">Route::get('\/', 'PostController@all');<\/pre>\n<p>In the\u00a0routes\/web.php\u00a0file, there will already be a route definition for the\u00a0\/\u00a0address, you will have to replace it with the new route definition above.<br \/>\nThe second route will handle requests for specific\u00a0Post\u00a0items and will be handled by the\u00a0PostController@single\u00a0action:<\/p>\n<pre class=\"lang:default decode:true \">Route::get('\/posts\/{post}', 'PostController@single');<\/pre>\n<p>With these two new routes added, here\u2019s what the\u00a0routes\/web.php\u00a0file should look like this:<\/p>\n<pre class=\"lang:default decode:true \">\/\/laravel-cms\/routes\/web.php\r\n\/*Route::get('\/', function () {\r\n    return view('welcome');\r\n});*\/\r\nAuth::routes();\r\n\r\nRoute::get('\/home', 'HomeController@index')-&gt;name('home');\r\n\r\nRoute::get('\/logout', 'Auth\\LoginController@logout')-&gt;name('logout' );\r\n\r\nRoute::get('\/', PostController@all);\r\nRoute::get('\/posts\/{post}', PostController@single);<\/pre>\n<p>SETTING UP THE POST CONTROLLER<br \/>\nIn this section, we want to define the handler action methods that we registered in the\u00a0routes\/web.php\u00a0file so that our application know how to render the matching views.<\/p>\n<p>First, let\u2019s add the\u00a0all()\u00a0method:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/ File: .\/app\/Http\/Controllers\/PostController.php\r\n    public function all()\r\n    {\r\n        return view('landing', [\r\n            'posts' =&gt; Post::latest()-&gt;paginate(5)\r\n        ]);\r\n    }<\/pre>\n<p>Here, we want to retrieve five created posts per page and send to the\u00a0landing\u00a0view. We will create this view shortly.<\/p>\n<p>Next, let\u2019s add the\u00a0single()\u00a0method to the controller:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/ File: .\/app\/Http\/Controllers\/PostController.php\r\n    public function single(Post $post)\r\n    {\r\n        return view('single', compact('post'));\r\n    }<\/pre>\n<p>In the method above, we used a feature of Laravel named\u00a0route model binding\u00a0to map the URL parameter to a\u00a0Post\u00a0instance with the same ID. We are returning a\u00a0single\u00a0view, which we will create shortly. This will be the view for the single post page.<\/p>\n<p>BUILDING OUR VIEWS<br \/>\nLaravel uses a templating engine called Blade for its frontend. We will use Blade to build these parts of the frontend before switching to Vue in the next chapter.<\/p>\n<p>Navigate to the\u00a0resources\/views\u00a0folder and create two new Blade files. They are landing.blade.php and single.blade.php<\/p>\n<p>These are the files that will load the views for the landing page and single post page. Before we start writing any code in these files, we want to create a simple layout template that our page views can use as a base.<\/p>\n<p>In the\u00a0resources\/views\/layouts\u00a0folder, create a Blade template file and call it\u00a0master.blade.php. This is where we will define the inheritable template for our single and landing pages.<\/p>\n<p>Open the\u00a0master.blade.php\u00a0file and update it with this code:<\/p>\n<pre class=\"lang:default decode:true\">&lt;!-- File: .\/resources\/views\/layouts\/master.blade.php --&gt;\r\n&lt;!DOCTYPE html&gt;\r\n&lt;html lang=\"en\"&gt;\r\n  &lt;head&gt;\r\n    &lt;meta charset=\"utf-8\"&gt;\r\n    &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\"&gt;\r\n    &lt;meta name=\"description\" content=\"\"&gt;\r\n    &lt;meta name=\"author\" content=\"Satria Faestha\"&gt;\r\n    &lt;title&gt;LaravelCMS&lt;\/title&gt;\r\n    &lt;link rel=\"stylesheet\" href=\"https:\/\/stackpath.bootstrapcdn.com\/bootstrap\/4.1.3\/css\/bootstrap.min.css\"&gt;\r\n    &lt;style&gt; \r\n    body {\r\n      padding-top: 54px;\r\n    }\r\n    @media (min-width: 992px) {\r\n      body {\r\n          padding-top: 56px;\r\n      }\r\n    }\r\n    &lt;\/style&gt;\r\n  &lt;\/head&gt;\r\n  &lt;body&gt;\r\n    &lt;nav class=\"navbar navbar-expand-lg navbar-dark bg-dark fixed-top\"&gt;\r\n      &lt;div class=\"container\"&gt;\r\n        &lt;a class=\"navbar-brand\" href=\"\/\"&gt;LaravelCMS&lt;\/a&gt;\r\n        &lt;div class=\"collapse navbar-collapse\" id=\"navbarResponsive\"&gt;\r\n          &lt;ul class=\"navbar-nav ml-auto\"&gt;\r\n             @if (Route::has('login'))\r\n                @auth\r\n                &lt;li class=\"nav-item\"&gt;\r\n                     &lt;a class=\"nav-link\" href=\"{{ url('\/home') }}\"&gt;Home&lt;\/a&gt;\r\n                &lt;\/li&gt;\r\n                &lt;li class=\"nav-item\"&gt;\r\n                  &lt;a class=\"nav-link\" href=\"{{ route('logout') }}\"\r\n                                       onclick=\"event.preventDefault();document.getElementById('logout-form').submit();\"&gt;\r\n                    Log out ({{ Auth::user()-&gt;name }})\r\n                  &lt;\/a&gt;\r\n                  &lt;form id=\"logout-form\" action=\"{{ route('logout') }}\" method=\"POST\" style=\"display: none;\"&gt;\r\n                    @csrf\r\n                  &lt;\/form&gt;\r\n                 &lt;\/li&gt;\r\n                 @else\r\n                 &lt;li class=\"nav-item\"&gt;\r\n                     &lt;a class=\"nav-link\" href=\"{{ route('login') }}\"&gt;Login&lt;\/a&gt;\r\n                &lt;\/li&gt;\r\n                 &lt;li class=\"nav-item\"&gt;\r\n                     &lt;a class=\"nav-link\" href=\"{{ route('register') }}\"&gt;Register&lt;\/a&gt;\r\n                 &lt;\/li&gt;\r\n                 @endauth\r\n             @endif\r\n          &lt;\/ul&gt;\r\n        &lt;\/div&gt;\r\n      &lt;\/div&gt;\r\n    &lt;\/nav&gt;\r\n\r\n    &lt;div id=\"app\"&gt;\r\n        @yield('content')\r\n    &lt;\/div&gt;\r\n\r\n    &lt;footer class=\"py-5 bg-dark\"&gt;\r\n      &lt;div class=\"container\"&gt;\r\n        &lt;p class=\"m-0 text-center text-white\"&gt;Copyright &amp;copy; LaravelCMS 2018&lt;\/p&gt;\r\n      &lt;\/div&gt;\r\n    &lt;\/footer&gt;\r\n  &lt;\/body&gt;\r\n&lt;\/html&gt;<\/pre>\n<p>Now we can inherit this template in the\u00a0landing.blade.php\u00a0file, open it and update it with this code:<\/p>\n<pre class=\"lang:default decode:true \">{{-- File: .\/resources\/views\/landing.blade.php --}}\r\n@extends('layouts.master')\r\n\r\n@section('content')\r\n&lt;div class=\"container\"&gt;\r\n  &lt;div class=\"row align-items-center\"&gt;\r\n    &lt;div class=\"col-md-8 mx-auto\"&gt;\r\n      &lt;h1 class=\"my-4 text-center\"&gt;Welcome to the Blog &lt;\/h1&gt;\r\n\r\n      @foreach ($posts as $post)\r\n      &lt;div class=\"card mb-4\"&gt;\r\n        &lt;img class=\"card-img-top\" src=\" {!! !empty($post-&gt;image) ? '\/uploads\/posts\/' . $post-&gt;image :  'http:\/\/placehold.it\/750x300' !!} \" alt=\"Card image cap\"&gt;\r\n        &lt;div class=\"card-body\"&gt;\r\n          &lt;h2 class=\"card-title text-center\"&gt;{{ $post-&gt;title }}&lt;\/h2&gt;\r\n          &lt;p class=\"card-text\"&gt; {{ str_limit($post-&gt;body, $limit = 280, $end = '...') }} &lt;\/p&gt;\r\n          &lt;a href=\"\/posts\/{{ $post-&gt;id }}\" class=\"btn btn-primary\"&gt;Read More &amp;rarr;&lt;\/a&gt;\r\n        &lt;\/div&gt;\r\n        &lt;div class=\"card-footer text-muted\"&gt;\r\n          Posted {{ $post-&gt;created_at-&gt;diffForHumans() }} by\r\n          &lt;a href=\"#\"&gt;{{ $post-&gt;user-&gt;name }} &lt;\/a&gt;\r\n        &lt;\/div&gt;\r\n      &lt;\/div&gt;\r\n      @endforeach\r\n\r\n    &lt;\/div&gt;\r\n  &lt;\/div&gt;\r\n&lt;\/div&gt;\r\n@endsection<\/pre>\n<p>Let\u2019s do the same with the\u00a0single.blade.php\u00a0file, open it and update it with this code:<\/p>\n<pre class=\"lang:default decode:true\">{{-- File: .\/resources\/views\/single.blade.php --}}\r\n@extends('layouts.master')\r\n\r\n@section('content')\r\n&lt;div class=\"container\"&gt;\r\n  &lt;div class=\"row\"&gt;\r\n    &lt;div class=\"col-lg-10 mx-auto\"&gt;\r\n      &lt;h3 class=\"mt-4\"&gt;{{ $post-&gt;title }} &lt;span class=\"lead\"&gt; by &lt;a href=\"#\"&gt; {{ $post-&gt;user-&gt;name }} &lt;\/a&gt;&lt;\/span&gt; &lt;\/h3&gt;\r\n      &lt;hr&gt;\r\n      &lt;p&gt;Posted {{ $post-&gt;created_at-&gt;diffForHumans() }} &lt;\/p&gt;\r\n      &lt;hr&gt;\r\n      &lt;img class=\"img-fluid rounded\" src=\" {!! !empty($post-&gt;image) ? '\/uploads\/posts\/' . $post-&gt;image :  'http:\/\/placehold.it\/750x300' !!} \" alt=\"\"&gt;\r\n      &lt;hr&gt;\r\n      &lt;p class=\"lead\"&gt;{{ $post-&gt;body }}&lt;\/p&gt;\r\n      &lt;hr&gt;\r\n      &lt;div class=\"card my-4\"&gt;\r\n        &lt;h5 class=\"card-header\"&gt;Leave a Comment:&lt;\/h5&gt;\r\n        &lt;div class=\"card-body\"&gt;\r\n          &lt;form&gt;\r\n            &lt;div class=\"form-group\"&gt;\r\n              &lt;textarea class=\"form-control\" rows=\"3\"&gt;&lt;\/textarea&gt;\r\n            &lt;\/div&gt;\r\n            &lt;button type=\"submit\" class=\"btn btn-primary\"&gt;Submit&lt;\/button&gt;\r\n          &lt;\/form&gt;\r\n        &lt;\/div&gt;\r\n      &lt;\/div&gt;\r\n    &lt;\/div&gt;\r\n  &lt;\/div&gt;\r\n&lt;\/div&gt;\r\n@endsection<\/pre>\n<p>TESTING THE APPLICATION<br \/>\nWe can test the application to see that things work as we expect. When we serve the application, we expect to see a landing page and a single post page. We also expect to see two posts because that\u2019s the number of posts we seeded into the database.<\/p>\n<p>We will serve the application using this command:<\/p>\n<pre class=\"lang:default decode:true \">php artisan serve<\/pre>\n<p>We have used simple placeholder images here because we haven\u2019t built the admin dashboard that allows CRUD operations to be performed on posts.<\/p>\n<p>In the coming chapters, we will add the ability for an admin to include a custom image when creating a new post.<\/p>\n<p>CONCLUSION<br \/>\nIn this chapter, we created the\u00a0Post\u00a0model and defined a relationship on it to the\u00a0User\u00a0model. We also built the landing page and single page.<\/p>\n<p>We will start building the admin frontend of the CMS. This is the first part of the series where we will integrate Vue and explore Vue\u2019s magical abilities.<\/p>\n<p>Laravel ships with Vue out of the box so we do not need to use the Vue-CLI or reference Vue from a CDN.\u00a0 Please see package.json file on the project root.<\/p>\n<pre class=\"lang:default decode:true \">     \/\/laravel-cms\/package.json\r\n     \"devDependencies\": {\r\n        \"axios\": \"^0.18\",\r\n        \"bootstrap\": \"^4.0.0\",\r\n        \"cross-env\": \"^5.1\",\r\n        \"jquery\": \"^3.2\",\r\n        \"laravel-mix\": \"^4.0.7\",\r\n        \"lodash\": \"^4.17.5\",\r\n        \"popper.js\": \"^1.12\",\r\n        \"resolve-url-loader\": \"^2.3.1\",\r\n        \"sass\": \"^1.15.2\",\r\n        \"sass-loader\": \"^7.1.0\",\r\n        \"vue\": \"^2.5.17\"\r\n    },<\/pre>\n<p>This makes it possible for us to have all of our application, the frontend, and backend, in a single codebase.<\/p>\n<p>Every newly created instance of a Laravel installation has some Vue files included by default, we can see these files when we navigate into the\u00a0resources\/js\/components\u00a0folder.<\/p>\n<p>SETTING UP VUE AND VUEROUTER<br \/>\nBefore we can start using Vue in our application, we need to first install some dependencies using NPM. To install the dependencies that come by default with Laravel, run the command below:<\/p>\n<pre class=\"lang:default decode:true \">npm install<\/pre>\n<p>We will be managing all of the routes for the admin dashboard using\u00a0vue-router\u00a0so let\u2019s pull it in:<\/p>\n<pre class=\"lang:default decode:true \">npm install --save vue-router<\/pre>\n<p>When the installation is complete, the next thing we want to do is open the\u00a0resources\/js\/app.js\u00a0file and replace its contents with the code below:<\/p>\n<pre class=\"lang:default decode:true\">    \/\/ File: .\/resources\/js\/app.js\r\n    require('.\/bootstrap');\r\n\r\n    import Vue from 'vue'\r\n    import VueRouter from 'vue-router'\r\n    import Homepage from '.\/components\/Homepage'\r\n    import Read from '.\/components\/Read'\r\n\r\n    Vue.use(VueRouter)\r\n\r\n    const router = new VueRouter({\r\n        mode: 'history',\r\n        routes: [\r\n            {\r\n                path: '\/admin\/dashboard',\r\n                name: 'read',\r\n                component: Read,\r\n                props: true\r\n            },\r\n        ],\r\n    });\r\n\r\n    const app = new Vue({\r\n        el: '#app',\r\n        router,\r\n        components: { Homepage },\r\n    });<\/pre>\n<p>In the snippet above, we imported the\u00a0VueRouter\u00a0and added it to the Vue application. We also imported a\u00a0Homepage\u00a0and a\u00a0Read\u00a0component. These are the components where we will write our markup so let\u2019s create both files.<\/p>\n<p>NOTE: IF YOU EDIT THE JS\/VUE TEMPLATE, DON&#8217;T FORGET TO STOP &#8216;php artisan serve&#8217; THEN RUN &#8216;npm run dev&#8217; THEN START AGAIN &#8216;php artisan serve&#8217;. IF NOT, YOU&#8217;D NOT SEE THE CHANGES YOU MADE!!!<\/p>\n<p>Open the\u00a0resources\/js\/components\u00a0folder and create four files:<br \/>\nHomepage.vue\u00a0&#8211; this will be the parent component for the admin dashboard frontend.<br \/>\nRead.vue\u00a0&#8211; this will be component that displays all the available posts on the admin dashboard.<br \/>\nCreate.vue\u00a0&#8211; this will be the component where an admin user can create a new post.<br \/>\nUpdate.vue\u00a0&#8211; this will be the component that displays the view where an admin user can update an existing post.<br \/>\nNote that we didn\u2019t create a component file for the delete operation, this is because it is going to be possible to delete a post from the\u00a0Read\u00a0component. There is no need for a view.<br \/>\nIn the\u00a0resources\/js\/app.js\u00a0file, we defined a\u00a0routes\u00a0array and in it, we registered a\u00a0read\u00a0route. During render time, this route\u2019s path will be mapped to the\u00a0Read\u00a0component.<\/p>\n<p>In the previous article, we specified that admin users should be shown an\u00a0admin.dashboard\u00a0view in the\u00a0index method, however, we didn\u2019t create this view. Let\u2019s create the view. Open the\u00a0resources\/views\u00a0folder and create a new folder called\u00a0admin. Within the new\u00a0resources\/views\/admin\u00a0folder, create a new file and called\u00a0dashboard.blade.php. This is going to be the entry point to the admin dashboard, further from this route, we will let the\u00a0Vue Router\u00a0handle everything else.<\/p>\n<p>Open the\u00a0resources\/views\/admin\/dashboard.blade.php\u00a0file and paste in the following code:<\/p>\n<pre class=\"lang:default decode:true \">    &lt;!-- File: .\/resources\/views\/admin\/dashboard.blade.php --&gt;\r\n    &lt;!DOCTYPE html&gt;\r\n    &lt;html lang=\"en\"&gt;\r\n    &lt;head&gt;\r\n        &lt;meta charset=\"UTF-8\"&gt;\r\n        &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"&gt;\r\n        &lt;meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\"&gt;\r\n        &lt;title&gt; Welcome to the Admin dashboard &lt;\/title&gt;\r\n        &lt;link rel=\"stylesheet\" href=\"https:\/\/stackpath.bootstrapcdn.com\/bootstrap\/4.1.3\/css\/bootstrap.min.css\"&gt;\r\n        &lt;style&gt;\r\n            html, body {\r\n            background-color: #202B33;\r\n            color: #738491;\r\n            font-family: \"Open Sans\";\r\n            font-size: 16px;\r\n            font-smoothing: antialiased;\r\n            overflow: hidden;\r\n            }\r\n        &lt;\/style&gt;\r\n    &lt;\/head&gt;\r\n    &lt;body&gt;\r\n\r\n      &lt;script src=\"{{ asset('js\/app.js') }}\"&gt;&lt;\/script&gt;\r\n    &lt;\/body&gt;\r\n    &lt;\/html&gt;<\/pre>\n<p>Our goal here is to integrate Vue into the application, so we included the\u00a0resources\/js\/app.js\u00a0file with this line of code:<\/p>\n<pre class=\"lang:default decode:true \">&lt;script src=\"{{ asset('js\/app.js') }}\"&gt;&lt;\/script&gt;<\/pre>\n<p>For our app to work, we need a root element to bind our Vue instance unto. Before the\u00a0&lt;script&gt;\u00a0tag, add this snippet of code:<\/p>\n<pre class=\"lang:default decode:true\">    ...\r\n    &lt;body&gt;\r\n        &lt;div id=\"app\"&gt;\r\n            &lt;Homepage \r\n                :user-name='@json(auth()-&gt;user()-&gt;name)' \r\n                :user-id='@json(auth()-&gt;user()-&gt;id)'&gt;\r\n            &lt;\/Homepage&gt;\r\n        &lt;\/div&gt;\r\n        &lt;script src=\"{{ asset('js\/app.js') }}\"&gt;&lt;\/script&gt;\r\n    &lt;\/body&gt;\r\n    ...<\/pre>\n<p>We earlier defined the\u00a0Homepage\u00a0component as the wrapping component, that\u2019s why we pulled it in here as the root component. For some of the frontend components to work correctly, we require some details of the logged in admin user to perform CRUD operations. This is why we passed down the\u00a0userName (:user-name) and\u00a0userId (:user-id) props to the\u00a0Homepage\u00a0component.<\/p>\n<p>We need to prevent the\u00a0CSRF\u00a0error from occurring in our Vue frontend, so include this snippet of code just before the\u00a0&lt;title&gt;\u00a0tag:<\/p>\n<pre class=\"lang:default decode:true \">&lt;meta name=\"csrf-token\" content=\"{{ csrf_token() }}\"&gt;\r\n&lt;script&gt; window.Laravel = { csrfToken: 'csrf_token() ' } &lt;\/script&gt;<\/pre>\n<p>This snippet will ensure that the correct token is always included in our frontend, Laravel provides the\u00a0CSRFprotection for us out of the box.<\/p>\n<p>SETTING UP THE HOMEPAGE VIEW<br \/>\nOpen the\u00a0Homepage.vue\u00a0file that we created some time ago and include this markup template:<\/p>\n<pre class=\"lang:default decode:true\">    &lt;!-- File: .\/resources\/js\/components\/Homepage.vue --&gt;\r\n    &lt;template&gt;\r\n      &lt;div&gt;\r\n        &lt;nav&gt;\r\n          &lt;section&gt;\r\n            &lt;a style=\"color: white\" href=\"\/admin\/dashboard\"&gt;Laravel-CMS&lt;\/a&gt; &amp;nbsp; ||  &amp;nbsp;\r\n            &lt;a style=\"color: white\" href=\"\/\"&gt;HOME&lt;\/a&gt;\r\n            &lt;hr&gt;\r\n            &lt;ul&gt;\r\n               &lt;li&gt;\r\n                 &lt;router-link :to=\"{ name: 'create', params: { userId } }\"&gt;\r\n                   NEW POST\r\n                 &lt;\/router-link&gt;\r\n               &lt;\/li&gt;\r\n            &lt;\/ul&gt;\r\n          &lt;\/section&gt;\r\n        &lt;\/nav&gt;\r\n        &lt;article&gt;\r\n          &lt;header&gt;\r\n            &lt;header class=\"d-inline\"&gt;Welcome, {{ userName }}&lt;\/header&gt;\r\n            &lt;p @click=\"logout\" class=\"float-right mr-3\" style=\"cursor: pointer\"&gt;Logout&lt;\/p&gt;\r\n          &lt;\/header&gt;\r\n          &lt;div&gt; \r\n            &lt;router-view&gt;&lt;\/router-view&gt; \r\n          &lt;\/div&gt;\r\n        &lt;\/article&gt;\r\n      &lt;\/div&gt;\r\n    &lt;\/template&gt;<\/pre>\n<p>We added a\u00a0router-link\u00a0in this template, which routes to the\u00a0Create\u00a0component.<\/p>\n<p>We are passing the\u00a0userId\u00a0data to the\u00a0create\u00a0component because a\u00a0userId\u00a0is required during\u00a0Post\u00a0creation.<\/p>\n<p>UPDATED: I want to pass more variables (like user-email and user-admin. DON&#8217;T USE SINGLE WORD VARIABLE LIKE title. USE DASH SEPARATED VARIABLE) to vue template. So modify resources\/views\/admin\/dashboard.blade.php:<\/p>\n<pre class=\"lang:default decode:true \">    &lt;body&gt;\r\n        &lt;div id=\"app\"&gt;\r\n            &lt;Homepage \r\n                :user-name='@json(auth()-&gt;user()-&gt;name)' \r\n                :user-id='@json(auth()-&gt;user()-&gt;id)' \r\n                :user-email='@json(auth()-&gt;user()-&gt;email)' \r\n                :user-admin='@json(auth()-&gt;user()-&gt;hasRole(\"admin\"))'&gt;\r\n            &lt;\/Homepage&gt;\r\n        &lt;\/div&gt;\r\n        &lt;script src=\"{{ asset('js\/app.js') }}\"&gt;&lt;\/script&gt;\r\n    &lt;\/body&gt;<\/pre>\n<p>Then catch the variables in resources\/js\/components\/Homepage.vue:<\/p>\n<pre class=\"lang:default decode:true \">&lt;script&gt;\r\nexport default {\r\n  name: \"Homepage\",\r\n  props: {\r\n    userId: {\r\n      type: Number,\r\n      required: true\r\n    },\r\n    userName: {\r\n      type: String,\r\n      required: true\r\n    },\r\n    userEmail: {\r\n      type: String,\r\n      required:  true\r\n    },\r\n    userAdmin: {\r\n      type: Boolean,\r\n      required:  true\r\n    }\r\n  },\r\n...<\/pre>\n<p>Here is the passing variables in Vue chrome toolbars<a href=\"http:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/Screenshot-from-2019-01-29-11-59-11.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-3453\" src=\"http:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/Screenshot-from-2019-01-29-11-59-11-1024x576.png\" alt=\"\" width=\"840\" height=\"473\" srcset=\"https:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/Screenshot-from-2019-01-29-11-59-11-1024x576.png 1024w, https:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/Screenshot-from-2019-01-29-11-59-11-300x169.png 300w, https:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/Screenshot-from-2019-01-29-11-59-11-768x432.png 768w, https:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/Screenshot-from-2019-01-29-11-59-11-1200x675.png 1200w, https:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/Screenshot-from-2019-01-29-11-59-11.png 1366w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/a>How to get user role name on the vue template???<br \/>\nI need to create a helper function in User model (app\/User.php):<\/p>\n<pre class=\"lang:default decode:true \">    public function getRole(){\r\n        return $this-&gt;roles()-&gt;first()-&gt;name;\r\n    }<\/pre>\n<p>Ref: https:\/\/stackoverflow.com\/questions\/36719279\/laravel-5-2-how-to-get-the-role-of-current-user-when-using-zizaco-entrust<br \/>\nThen modify again resources\/views\/admin\/dashboard.blade.php to get the user role with auth()-&gt;user()-&gt;getRole():<\/p>\n<pre class=\"lang:default decode:true\">            &lt;Homepage \r\n                :user-name='@json(auth()-&gt;user()-&gt;name)' \r\n                :user-id='@json(auth()-&gt;user()-&gt;id)' \r\n                :user-email='@json(auth()-&gt;user()-&gt;email)' \r\n                :user-admin='@json(auth()-&gt;user()-&gt;hasRole(\"admin\"))' \r\n                :user-role='@json(auth()-&gt;user()-&gt;getRole())'&gt;\r\n            &lt;\/Homepage&gt;<\/pre>\n<p>Then catch the props value in resources\/js\/components\/Homepage.vue:<\/p>\n<pre class=\"lang:default decode:true \">  props: {\r\n    ...\r\n    userRole: {\r\n      type: String,\r\n      required:  true\r\n    }\r\n  },<\/pre>\n<p>&nbsp;<\/p>\n<p>Let\u2019s include some styles so that the page looks good. Below the closing\u00a0template\u00a0tag, paste the following code:<\/p>\n<pre class=\"lang:default decode:true \">   &lt;style scoped&gt;\r\n      @import url(https:\/\/fonts.googleapis.com\/css?family=Dosis:300|Lato:300,400,600,700|Roboto+Condensed:300,700|Open+Sans+Condensed:300,600|Open+Sans:400,300,600,700|Maven+Pro:400,700);\r\n      @import url(\"https:\/\/netdna.bootstrapcdn.com\/font-awesome\/4.2.0\/css\/font-awesome.css\");\r\n      * {\r\n        -moz-box-sizing: border-box;\r\n        -webkit-box-sizing: border-box;\r\n        box-sizing: border-box;\r\n      }\r\n      header {\r\n        color: #d3d3d3;\r\n      }\r\n      nav {\r\n        position: absolute;\r\n        top: 0;\r\n        bottom: 0;\r\n        right: 82%;\r\n        left: 0;\r\n        padding: 22px;\r\n        border-right: 2px solid #161e23;\r\n      }\r\n      nav &gt; header {\r\n        font-weight: 700;\r\n        font-size: 0.8rem;\r\n        text-transform: uppercase;\r\n      }\r\n      nav section {\r\n        font-weight: 600;\r\n      }\r\n      nav section header {\r\n        padding-top: 30px;\r\n      }\r\n      nav section ul {\r\n        list-style: none;\r\n        padding: 0px;\r\n      }\r\n      nav section ul a {\r\n        color: white;\r\n        text-decoration: none;\r\n        font-weight: bold;\r\n      }\r\n      article {\r\n        position: absolute;\r\n        top: 0;\r\n        bottom: 0;\r\n        right: 0;\r\n        left: 18%;\r\n        overflow: auto;\r\n        border-left: 2px solid #2a3843;\r\n        padding: 20px;\r\n      }\r\n      article &gt; header {\r\n        height: 60px;\r\n        border-bottom: 1px solid #2a3843;\r\n      }\r\n    &lt;\/style&gt;<\/pre>\n<p>We are using the scoped attribute on the\u00a0&lt;style&gt;\u00a0tag because we want the CSS to only be applied on the\u00a0Homepage\u00a0component.<br \/>\nNext, let&#8217;s add the\u00a0&lt;script&gt;\u00a0section that will use the props we passed down from the parent component. We will also define the method that controls the\u00a0log out\u00a0feature here. Below the closing\u00a0style\u00a0tag, paste the following code:<\/p>\n<pre class=\"lang:default decode:true\">    &lt;script&gt;\r\n    export default {\r\n      name: \"Homepage\",\r\n      props: {\r\n        userId: {\r\n          type: Number,\r\n          required: true\r\n        },\r\n        userName: {\r\n          type: String,\r\n          required: true\r\n        }\r\n      },\r\n      data() {\r\n        return {};\r\n      },\r\n      methods: {\r\n        logout() {\r\n          axios.post(\"\/logout\").then(() =&gt; {\r\n            window.location = \"\/\";\r\n          });\r\n        }\r\n      }\r\n    };\r\n    &lt;\/script&gt;<\/pre>\n<p>SETTING UP THE READ VIEW<br \/>\nIn the\u00a0resources\/js\/app.js\u00a0file, we defined the path of the\u00a0read\u00a0component as\u00a0\/admin\/dashboard, which is the same address as the\u00a0Homepage\u00a0component. This will make sure the\u00a0Read\u00a0component always loads by default.<\/p>\n<p>In the\u00a0Read\u00a0component, we want to load all of the available posts. We are also going to add\u00a0Update\u00a0and\u00a0Delete options to each post. Clicking on these options will lead to the\u00a0update\u00a0and\u00a0delete\u00a0views respectively.<\/p>\n<p>Open the\u00a0Read.vue\u00a0file and paste the following:<\/p>\n<pre class=\"lang:default decode:true \">    &lt;!-- File: .\/resources\/js\/components\/Read.vue --&gt;\r\n    &lt;template&gt;\r\n        &lt;div id=\"posts\"&gt;\r\n            &lt;p class=\"border p-3\" v-for=\"post in posts\"&gt;\r\n                {{ post.title }}\r\n                &lt;router-link :to=\"{ name: 'update', params: { postId : post.id } }\"&gt;\r\n                    &lt;button type=\"button\" class=\"p-1 mx-3 float-right btn btn-light\"&gt;\r\n                        Update\r\n                    &lt;\/button&gt;\r\n                &lt;\/router-link&gt;\r\n                &lt;button \r\n                    type=\"button\" \r\n                    @click=\"deletePost(post.id)\" \r\n                    class=\"p-1 mx-3 float-right btn btn-danger\"\r\n                &gt;\r\n                    Delete\r\n                &lt;\/button&gt;\r\n            &lt;\/p&gt;\r\n            &lt;div&gt;\r\n                &lt;button \r\n                    v-if=\"next\" \r\n                    type=\"button\" \r\n                    @click=\"navigate(next)\" \r\n                    class=\"m-3 btn btn-primary\"\r\n                &gt;\r\n                  Next\r\n                &lt;\/button&gt;\r\n                &lt;button \r\n                    v-if=\"prev\" \r\n                    type=\"button\" \r\n                    @click=\"navigate(prev)\" \r\n                    class=\"m-3 btn btn-primary\"\r\n                &gt;\r\n                  Previous\r\n                &lt;\/button&gt;\r\n            &lt;\/div&gt;\r\n        &lt;\/div&gt;\r\n    &lt;\/template&gt;<\/pre>\n<p>Above, we have the template to handle the posts that are loaded from the API (Will get the data via API later). Next, paste the following below the closing\u00a0template\u00a0tag:<\/p>\n<pre class=\"lang:default decode:true \">    &lt;script&gt;\r\n    export default {\r\n      mounted() {\r\n        this.getPosts();\r\n      },\r\n      data() {\r\n        return {\r\n          posts: {},\r\n          next: null,\r\n          prev: null\r\n        };\r\n      },\r\n      methods: {\r\n        getPosts(address) {\r\n          axios.get(address ? address : \"\/api\/posts\").then(response =&gt; {\r\n            this.posts = response.data.data;\r\n            this.prev = response.data.links.prev;\r\n            this.next = response.data.links.next;\r\n          });\r\n        },\r\n        deletePost(id) {\r\n          axios.delete(\"\/api\/posts\/\" + id).then(response =&gt; this.getPosts())\r\n        },\r\n        navigate(address) {\r\n          this.getPosts(address)\r\n        }\r\n      }\r\n    };\r\n    &lt;\/script&gt;<\/pre>\n<p>In the script above, we defined a\u00a0getPosts()\u00a0method that requests a list of posts from the backend server. We also defined a\u00a0posts\u00a0object as a data property. This object will be populated whenever posts are received from the backend server.<\/p>\n<p>We defined\u00a0next\u00a0and\u00a0prev\u00a0data string properties to store pagination links and only display the pagination options where it is available.<\/p>\n<p>Lastly, we defined a\u00a0deletePost()\u00a0method that takes the\u00a0id\u00a0of a post as a parameter and sends a\u00a0DELETE request to the API interface using\u00a0Axios.<\/p>\n<p>TESTING THE APPLICATION<br \/>\nNow that we have completed the first few components, we can serve the application using this command:<\/p>\n<pre class=\"lang:default decode:true \">php artisan serve<\/pre>\n<p>We will also build the assets so that our JavaScript is compiled for us. To do this, will run the command below in the root of the project folder:<\/p>\n<pre class=\"lang:default decode:true\">npm run dev<\/pre>\n<p>We can visit the application\u2019s URL\u00a0http:\/\/localhost:8000\u00a0and log in as an admin user. It&#8217;d show the admin dashboard but no post will show up because we haven&#8217;t set up the &#8216;api&#8217; route yet.<br \/>\nNOTE: If you got an error like<\/p>\n<pre class=\"lang:default decode:true \">Unknown custom element: &lt;homepage&gt; - did you register the component correctly? For recursive components, make sure to provide the \"name\" option.<\/pre>\n<p>It because the app.js file not loaded. Probably you call it wrong. Make sure app.js in this path:<\/p>\n<pre class=\"lang:default decode:true \">laravel-cms\/resources\/js\/app.js<\/pre>\n<p>NOT in laravel-cms\/resources\/assets\/js\/app.js<\/p>\n<p>We will start building the API for the application. We will create an API for CRUD operations that an admin will perform on posts and we will test the endpoints using Postman.<\/p>\n<p>BUILDING THE API USING LARAVEL\u2019S API RESOURCES<br \/>\nThe Laravel framework makes it very easy to build APIs. It has an\u00a0API resources\u00a0feature that we can easily adopt in our project. You can think of API resources as a transformation layer between Eloquent models and the JSON responses that will be sent back by our API.<\/p>\n<p>ALLOWING MASS ASSIGNMENT ON SPECIFIED FIELDS<br \/>\nSince we are going to be performing CRUD operations on the posts in the application, we have to explicitly specify that it\u2019s permitted for some fields to be mass-assigned data. For security reasons, Laravel\u00a0prevents mass assignment\u00a0of data to model fields by default.<\/p>\n<p>Open the\u00a0Post.php\u00a0file and include this line of code:<\/p>\n<pre class=\"lang:default decode:true \">\/\/ File: .\/app\/Post.php\r\nprotected $fillable = ['user_id', 'title', 'body', 'image'];<\/pre>\n<p>DEFINING API ROUTES<br \/>\nWe will use the\u00a0apiResource() method to generate only API routes. Open the\u00a0routes\/api.php\u00a0file and add the following code:<\/p>\n<pre class=\"lang:default decode:true \">\/\/ File: .\/routes\/api.php\r\nRoute::apiResource('posts', 'PostController');<\/pre>\n<p>Because we will be handling the API requests on the\u00a0\/posts\u00a0URL using the\u00a0PostController, we will have to include some additional action methods in our post controller.<\/p>\n<p>CREATING THE POST RESOURCE<br \/>\nAt the beginning of this section, we already talked about what Laravel\u2019s API resources are. Here, we create a resource class for our\u00a0Post\u00a0model. This will enable us to retrieve\u00a0Post\u00a0data and return formatted JSON format.<\/p>\n<p>To create a resource class for our\u00a0Post\u00a0model run the following command in your terminal:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan make:resource PostResource\r\nResource created successfully.<\/pre>\n<p>A new\u00a0PostResource.php\u00a0file will be available in the\u00a0app\/Http\/Resources\u00a0directory of our application. Open up the\u00a0PostResource.php\u00a0file and replace the\u00a0toArray()\u00a0method with the following:<\/p>\n<pre class=\"lang:default decode:true\">    \/\/ File: .\/app\/Http\/Resources\/PostResource.php\r\n    public function toArray($request)\r\n    {\r\n        \/\/return parent::toArray($request);\r\n        return [\r\n            'id' =&gt; $this-&gt;id,\r\n            'title' =&gt; $this-&gt;title,\r\n            'body' =&gt; $this-&gt;body,\r\n            'image' =&gt; $this-&gt;image,\r\n            'created_at' =&gt; (string) $this-&gt;created_at,\r\n            'updated_at' =&gt; (string) $this-&gt;updated_at,\r\n        ];\r\n    }<\/pre>\n<p>The job of this\u00a0toArray()\u00a0method is to convert our\u00a0Post\u00a0resource into an array. As seen above, we have specified the fields on our\u00a0Post\u00a0model, which we want to be returned as JSON when we make a request for posts.<\/p>\n<p>We are also explicitly casting the dates,\u00a0created_at\u00a0and\u00a0update_at, to strings so that they would be returned as date strings. The dates are normally an instance of\u00a0Carbon.<\/p>\n<p>Now that we have created a resource class for our\u00a0Post\u00a0model, we can start building the API\u2019s action methods in our\u00a0PostController\u00a0and return instances of the\u00a0PostResource\u00a0where we want.<\/p>\n<p>ADDING THE ACTION METHODS TO THE POST CONTROLLER<br \/>\nThe usual actions performed on a post include the following:<br \/>\nCreate &#8211; the process of creating a new post.<br \/>\nRead &#8211; the process of reading one or more posts.<br \/>\nUpdate &#8211; the process of updating an already published post.<br \/>\nDelete &#8211; the process of deleting a post.<br \/>\nIn the last article, we already implemented a kind of \u2018Read\u2019 functionality when we defined the\u00a0all\u00a0and\u00a0single methods. These methods allow users to browse through posts on the homepage.<\/p>\n<p>In this section, we will define the methods that will resolve our API requests for creating, reading, updating and deleting posts.<\/p>\n<p>The first thing we want to do is import the\u00a0PostResource\u00a0class at the top of the\u00a0PostController.php\u00a0file:<\/p>\n<pre class=\"lang:default decode:true \">\/\/ File: .\/app\/Http\/Controllers\/PostController.php\r\nuse App\\Http\\Resources\\PostResource;<\/pre>\n<p>Because we created the\u00a0PostController\u00a0as a resource controller, we already have the resource action methods included for us in the\u00a0PostController.php\u00a0file, we will be updating them with fitting snippets of code.<\/p>\n<p>BUILDING THE HANDLER ACTION FOR THE CREATE OPERATION<br \/>\nIn the\u00a0PostController\u00a0update the\u00a0store()\u00a0action method with the code snippet below. It will allow us to validate and create a new post:<\/p>\n<pre class=\"lang:default decode:true\">    \/\/ File: .\/app\/Http\/Controllers\/PostController.php\r\n    public function store(Request $request)\r\n    {\r\n        $this-&gt;validate($request, [\r\n            'title' =&gt; 'required',\r\n            'body' =&gt; 'required',\r\n            'user_id' =&gt; 'required',            \r\n            'image' =&gt; 'required|mimes:jpeg,png,jpg,gif,svg',\r\n        ]);\r\n\r\n        $post = new Post;\r\n\r\n        if ($request-&gt;hasFile('image')) {\r\n            $image = $request-&gt;file('image');\r\n            $name = str_slug($request-&gt;title).'.'.$image-&gt;getClientOriginalExtension();\r\n            $destinationPath = public_path('\/uploads\/posts');\r\n            $imagePath = $destinationPath . \"\/\" . $name;\r\n            $image-&gt;move($destinationPath, $name);\r\n            $post-&gt;image = $name;\r\n        }\r\n\r\n        $post-&gt;user_id = $request-&gt;user_id;\r\n        $post-&gt;title = $request-&gt;title;\r\n        $post-&gt;body = $request-&gt;body;\r\n        $post-&gt;save();\r\n\r\n        return new PostResource($post);\r\n    }<\/pre>\n<p>Here\u2019s a breakdown of what this method does:<br \/>\nReceives a new request.<br \/>\nValidates the request.<br \/>\nCreates a new post.<br \/>\nReturns the post as a\u00a0PostResource, which in turn returns a JSON formatted response.<\/p>\n<p>BUILDING THE HANDLER ACTION FOR THE READ OPERATIONS<br \/>\nWhat we want here is to be able to read all the created posts or a single post. This is possible because the\u00a0apiResource()\u00a0method defines the API routes using standard REST rules.<\/p>\n<p>This means that a\u00a0GET\u00a0request to this address,\u00a0http:\/\/localhost:8000\/api\/posts, should be resolved by the\u00a0index()\u00a0action method. Let\u2019s update the\u00a0index\u00a0method with the following code:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/ File: .\/app\/Http\/Controllers\/PostController.php\r\n    public function index()\r\n    {\r\n        return PostResource::collection(Post::latest()-&gt;paginate(5));\r\n    }<\/pre>\n<p>This method will allow us to return a JSON formatted collection of all of the stored posts. We also want to paginate the response as this will allow us to create a better view on the admin dashboard.<\/p>\n<p>Following the RESTful conventions as we discussed above, a\u00a0GET\u00a0request to this address,\u00a0http:\/\/localhost:8000\/api\/posts\/id, should be resolved by the\u00a0show()\u00a0action method. Let\u2019s update the method with the fitting snippet:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/ File: .\/app\/Http\/Controllers\/PostController.php\r\n    public function show(Post $post)\r\n    {\r\n        return new PostResource($post);\r\n    }<\/pre>\n<p>Awesome, now this method will return a single instance of a post resource upon API query.<\/p>\n<p>BUILDING THE HANDLER ACTION FOR THE UPDATE OPERATION<br \/>\nNext, let\u2019s update the\u00a0update()\u00a0method in the\u00a0PostController\u00a0class. It will allow us to modify an existing post:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/ File: .\/app\/Http\/Controllers\/PostController.php\r\n    public function update(Request $request, Post $post)\r\n    {\r\n        $this-&gt;validate($request, [\r\n            'title' =&gt; 'required',\r\n            'body' =&gt; 'required',\r\n        ]);\r\n\r\n        $post-&gt;update($request-&gt;only(['title', 'body']));\r\n\r\n        return new PostResource($post);\r\n    }<\/pre>\n<p>This method receives a request and a post\u00a0id\u00a0as parameters, then we use route model binding to resolve the\u00a0id into an instance of a\u00a0Post. First, we validate the\u00a0$request\u00a0attributes, then we update the title and body fields of the resolved post.<\/p>\n<p>BUILDING THE HANDLER ACTION FOR THE DELETE OPERATION<br \/>\nLet\u2019s update the\u00a0destroy()\u00a0method in the\u00a0PostController\u00a0class. This method will allow us to remove an existing post:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/ File: .\/app\/Http\/Controllers\/PostController.php\r\n    public function destroy(Post $post)\r\n    {\r\n        $post-&gt;delete();\r\n\r\n        return response()-&gt;json(null, 204);\r\n    }<\/pre>\n<p>In this method, we resolve the\u00a0Post\u00a0instance, then delete it and return a 204 response code.<\/p>\n<p>Our methods are complete. We have a method to handle our CRUD operations.<a href=\"http:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/admin_dashboard.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-3431\" src=\"http:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/admin_dashboard-1024x478.jpg\" alt=\"\" width=\"840\" height=\"392\" srcset=\"https:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/admin_dashboard-1024x478.jpg 1024w, https:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/admin_dashboard-300x140.jpg 300w, https:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/admin_dashboard-768x359.jpg 768w, https:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/admin_dashboard-1200x560.jpg 1200w, https:\/\/myprojects.advchaweb.com\/wp-content\/uploads\/2019\/01\/admin_dashboard.jpg 1351w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/a>We are not done with the dashboard just yet. In the next part, we will add the views that lets us create and update posts.<\/p>\n<p>we have built the first parts of the admin dashboard using Vue. We also made it into an SPA with the\u00a0VueRouter, this means that visiting the pages does not cause a reload to the web browser.<\/p>\n<p>We only built the wrapper component and the\u00a0Read\u00a0component that retrieves the posts to be loaded so an admin can manage them.<\/p>\n<p>we will build the view that will allow users to create and update posts. We will start writing code in the\u00a0Update.vue\u00a0and\u00a0Create.vue\u00a0files that we created in the previous article.<\/p>\n<p>When we are done with this part, we will have additional functionalities like create and updating.<\/p>\n<p>INCLUDING THE NEW ROUTES IN VUE ROUTER<br \/>\nIn the previous article, we only defined the route for the\u00a0Read\u00a0component, we need to include the route configuration for the new components that we are about to build;\u00a0Update\u00a0and\u00a0Create.<\/p>\n<p>Open the\u00a0resources\/js\/app.js\u00a0file and replace the contents with the code below:<\/p>\n<pre class=\"lang:default decode:true \">import Homepage from '.\/components\/Homepage'\r\nimport Create from '.\/components\/Create'\r\nimport Read from '.\/components\/Read'\r\nimport Update from '.\/components\/Update'\r\n\r\nVue.use(VueRouter)\r\n\r\nconst router = new VueRouter({\r\n    mode: 'history',\r\n    routes: [\r\n        {\r\n            path: '\/admin\/dashboard',\r\n            name: 'read',\r\n            component: Read,\r\n            props: true\r\n        },\r\n        {\r\n            path: '\/admin\/create',\r\n            name: 'create',\r\n            component: Create,\r\n            props: true\r\n        },\r\n        {\r\n            path: '\/admin\/update',\r\n            name: 'update',\r\n            component: Update,\r\n            props: true\r\n        },\r\n    ],\r\n});<\/pre>\n<p>Above, we have added two new components to the JavaScript file. We have the\u00a0Create\u00a0and\u00a0Read\u00a0components. We also added them to the\u00a0router\u00a0so that they can be loaded using the specified URLs.<\/p>\n<p>BUILDING THE CREATE VIEW<br \/>\nOpen the\u00a0Create.vue\u00a0file and update it with this markup template:<\/p>\n<pre class=\"lang:default decode:true\">    &lt;!-- File: .\/resources\/js\/components\/Create.vue --&gt;\r\n    &lt;template&gt;\r\n      &lt;div class=\"container\"&gt;\r\n        &lt;form&gt;\r\n          &lt;div :class=\"['form-group m-1 p-3', (successful ? 'alert-success' : '')]\"&gt;\r\n            &lt;span v-if=\"successful\" class=\"label label-sucess\"&gt;Published!&lt;\/span&gt;\r\n          &lt;\/div&gt;\r\n          &lt;div :class=\"['form-group m-1 p-3', error ? 'alert-danger' : '']\"&gt;\r\n            &lt;span v-if=\"errors.title\" class=\"label label-danger\"&gt;\r\n              {{ errors.title[0] }}\r\n            &lt;\/span&gt;\r\n            &lt;span v-if=\"errors.body\" class=\"label label-danger\"&gt; \r\n              {{ errors.body[0] }} \r\n            &lt;\/span&gt;\r\n            &lt;span v-if=\"errors.image\" class=\"label label-danger\"&gt; \r\n              {{ errors.image[0] }} \r\n            &lt;\/span&gt;\r\n          &lt;\/div&gt;\r\n\r\n          &lt;div class=\"form-group\"&gt;\r\n            &lt;input type=\"title\" ref=\"title\" class=\"form-control\" id=\"title\" placeholder=\"Enter title\" required&gt;\r\n          &lt;\/div&gt;\r\n\r\n          &lt;div class=\"form-group\"&gt;\r\n            &lt;textarea class=\"form-control\" ref=\"body\" id=\"body\" placeholder=\"Enter a body\" rows=\"8\" required&gt;&lt;\/textarea&gt;\r\n          &lt;\/div&gt;\r\n\r\n          &lt;div class=\"custom-file mb-3\"&gt;\r\n            &lt;input type=\"file\" ref=\"image\" name=\"image\" class=\"custom-file-input\" id=\"image\" required&gt;\r\n            &lt;label class=\"custom-file-label\" &gt;Choose file...&lt;\/label&gt;\r\n          &lt;\/div&gt;\r\n\r\n          &lt;button type=\"submit\" @click.prevent=\"create\" class=\"btn btn-primary block\"&gt;\r\n            Submit\r\n          &lt;\/button&gt;\r\n        &lt;\/form&gt;\r\n      &lt;\/div&gt;\r\n    &lt;\/template&gt;<\/pre>\n<p>Above we have the template for the\u00a0Create\u00a0component. If there is an error during post creation, there will be a field indicating the specific error. When a post is successfully published, there will also a message saying it was successful.<\/p>\n<p>Let\u2019s include the\u00a0script\u00a0logic that will perform the sending of posts to our backend server and read back the response.<\/p>\n<p>After the closing\u00a0template\u00a0tag add this:<\/p>\n<pre class=\"lang:default decode:true\">    &lt;script&gt;\r\n    export default {\r\n      props: {\r\n        userId: {\r\n          type: Number,\r\n          required: true\r\n        }\r\n      },\r\n      data() {\r\n        return {\r\n          error: false,\r\n          successful: false,\r\n          errors: []\r\n        };\r\n      },\r\n      methods: {\r\n        create() {\r\n          const formData = new FormData();\r\n          formData.append(\"title\", this.$refs.title.value);\r\n          formData.append(\"body\", this.$refs.body.value);\r\n          formData.append(\"user_id\", this.userId);\r\n          formData.append(\"image\", this.$refs.image.files[0]);\r\n\r\n          axios\r\n            .post(\"\/api\/posts\", formData)\r\n            .then(response =&gt; {\r\n              this.successful = true;\r\n              this.error = false;\r\n              this.errors = [];\r\n            })\r\n            .catch(error =&gt; {\r\n              if (!_.isEmpty(error.response)) {\r\n                if ((error.response.status = 422)) {\r\n                  this.errors = error.response.data.errors;\r\n                  this.successful = false;\r\n                  this.error = true;\r\n                }\r\n              }\r\n            });\r\n\r\n          this.$refs.title.value = \"\";\r\n          this.$refs.body.value = \"\";\r\n        }\r\n      }\r\n    };\r\n    &lt;\/script&gt;<\/pre>\n<p>In the script above, we defined a\u00a0create()\u00a0method that takes the values of the\u00a0input\u00a0fields and uses the\u00a0Axios library to send them to the API interface on the backend server. Within this method, we also update the status of the operation, so that an admin user can know when a post is created successfully or not.<\/p>\n<p>BUILDING THE UPDATE VIEW<br \/>\nLet\u2019s start building the\u00a0Update\u00a0component. Open the\u00a0Update.vue\u00a0file and update it with this markup template:<\/p>\n<pre class=\"lang:default decode:true \">    &lt;!-- File: .\/resources\/js\/components\/Update.vue --&gt;\r\n    &lt;template&gt;\r\n      &lt;div class=\"container\"&gt;\r\n        &lt;form&gt;\r\n          &lt;div :class=\"['form-group m-1 p-3', successful ? 'alert-success' : '']\"&gt;\r\n            &lt;span v-if=\"successful\" class=\"label label-sucess\"&gt;Updated!&lt;\/span&gt;\r\n          &lt;\/div&gt;\r\n\r\n          &lt;div :class=\"['form-group m-1 p-3', error ? 'alert-danger' : '']\"&gt;\r\n            &lt;span v-if=\"errors.title\" class=\"label label-danger\"&gt;\r\n              {{ errors.title[0] }}\r\n            &lt;\/span&gt;\r\n            &lt;span v-if=\"errors.body\" class=\"label label-danger\"&gt;\r\n              {{ errors.body[0] }}\r\n            &lt;\/span&gt;\r\n          &lt;\/div&gt;\r\n\r\n          &lt;div class=\"form-group\"&gt;\r\n            &lt;input type=\"title\" ref=\"title\" class=\"form-control\" id=\"title\" placeholder=\"Enter title\" required&gt;\r\n          &lt;\/div&gt;\r\n\r\n          &lt;div class=\"form-group\"&gt;\r\n            &lt;textarea class=\"form-control\" ref=\"body\" id=\"body\" placeholder=\"Enter a body\" rows=\"8\" required&gt;&lt;\/textarea&gt;\r\n          &lt;\/div&gt;\r\n\r\n          &lt;button type=\"submit\" @click.prevent=\"update\" class=\"btn btn-primary block\"&gt;\r\n            Submit\r\n          &lt;\/button&gt;\r\n        &lt;\/form&gt;\r\n      &lt;\/div&gt;\r\n    &lt;\/template&gt;<\/pre>\n<p>This template is similar to the one in the\u00a0Create\u00a0component. Let\u2019s add the\u00a0script\u00a0for the component.<\/p>\n<p>Below the closing\u00a0template\u00a0tag, paste the following:<\/p>\n<pre class=\"lang:default decode:true \">    &lt;script&gt;\r\n    export default {\r\n      mounted() {\r\n        this.getPost();\r\n      },\r\n      props: {\r\n        postId: {\r\n          type: Number,\r\n          required: true\r\n        }\r\n      },\r\n      data() {\r\n        return {\r\n          error: false,\r\n          successful: false,\r\n          errors: []\r\n        };\r\n      },\r\n      methods: {\r\n        update() {\r\n          let title = this.$refs.title.value;\r\n          let body = this.$refs.body.value;\r\n\r\n          axios\r\n            .put(\"\/api\/posts\/\" + this.postId, { title, body })\r\n            .then(response =&gt; {\r\n              this.successful = true;\r\n              this.error = false;\r\n              this.errors = [];\r\n            })\r\n            .catch(error =&gt; {\r\n              if (!_.isEmpty(error.response)) {\r\n                if ((error.response.status = 422)) {\r\n                  this.errors = error.response.data.errors;\r\n                  this.successful = false;\r\n                  this.error = true;\r\n                }\r\n              }\r\n            });\r\n        },\r\n        getPost() {\r\n          axios.get(\"\/api\/posts\/\" + this.postId).then(response =&gt; {\r\n            this.$refs.title.value = response.data.data.title;\r\n            this.$refs.body.value = response.data.data.body;\r\n          });\r\n        }\r\n      }\r\n    };\r\n    &lt;\/script&gt;<\/pre>\n<p>In the script above, we make a call to the\u00a0getPosts()\u00a0method as soon as the component is\u00a0mounted. The\u00a0getPosts()\u00a0method fetches the data of a single post from the backend server, using the\u00a0postId.<\/p>\n<p>When Axios sends back the data for the post, we update the input fields in this component so they can be updated.<\/p>\n<p>Finally, the\u00a0update()\u00a0method takes the values of the fields in the components and attempts to send them to the backend server for an update. In a situation where the fails, we get instant feedback.<\/p>\n<p>TESTING THE APPLICATION<br \/>\nTo test that our changes work, we want to refresh the database and restore it back to a fresh state. To do this, run the following command in your terminal:<\/p>\n<pre class=\"lang:default decode:true \">$ php artisan migrate:fresh --seed<\/pre>\n<p>Next, let\u2019s compile our JavaScript files and assets. This will make sure all the changes we made in the Vue component and the\u00a0app.js\u00a0file gets built. To recompile, run the command below in your terminal:<\/p>\n<pre class=\"lang:default decode:true \">$ npm run dev<\/pre>\n<p>Lastly, we need to serve the application. To do this, run the following command in your terminal window:<\/p>\n<pre class=\"lang:default decode:true \">$ php artisan serve<\/pre>\n<p>If you had the serve command running before, then you might need to restart it.<br \/>\nWe will visit the application\u2019s\u00a0http:\/\/localhost:8000\u00a0and log in as an admin user. From the dashboard, you can test the create and update feature<\/p>\n<p>Now, we will be adding support for comments. We will also ensure that the comments on each post are updated in realtime, so a user doesn\u2019t have to refresh the page to see new comments.<\/p>\n<p>ADDING COMMENTS TO THE BACKEND<br \/>\nWhen we were creating the API, we did not add the support for comments to the post resource, so we will have to do so now. Open the API project in your text editor as we will be modifying the project a little.<\/p>\n<p>The first thing we want to do is create a model, controller, and a migration for the comment resource. To do this, open your terminal and\u00a0cd\u00a0to the project directory and run the following command:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan make:model Comment -mc\r\nModel created successfully.\r\nCreated Migration: 2019_01_30_042953_create_comments_table\r\nController created successfully.<\/pre>\n<p>The command above will create a model called\u00a0Comment, a controller called\u00a0CommentController, and a migration file in the\u00a0database\/migrations\u00a0directory.<\/p>\n<p>UPDATING THE COMMENTS MIGRATION FILE<br \/>\nTo update the comments migration navigate to the\u00a0database\/migrations\u00a0folder and find the newly created migration file for the\u00a0Comment\u00a0model. Let\u2019s update the\u00a0up()\u00a0method in the file:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/database\/migrations\/2019_01_30_042953_create_comments_table.php\r\n    public function up()\r\n    {\r\n        Schema::create('comments', function (Blueprint $table) {\r\n            $table-&gt;increments('id');\r\n            $table-&gt;timestamps();\r\n            $table-&gt;integer('user_id')-&gt;unsigned();\r\n            $table-&gt;integer('post_id')-&gt;unsigned();\r\n            $table-&gt;text('body');\r\n        });\r\n    }<\/pre>\n<p>We included\u00a0user_id\u00a0and\u00a0post_id\u00a0fields because we intend to create a link between the comments, users, and posts. The\u00a0body\u00a0field will contain the actual comment.<\/p>\n<p>DEFINING THE RELATIONSHIPS AMONG THE COMMENT, USER, AND POST MODELS<br \/>\nIn this application, a comment will belong to a user and a post because a user can make a comment on a specific post, so we need to define the relationship that ties everything up.<\/p>\n<p>Open the\u00a0User\u00a0model and include this method:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/ File: .\/app\/User.php\r\n    public function comments()\r\n    {\r\n        return $this-&gt;hasMany(Comment::class);\r\n    }<\/pre>\n<p>This is a relationship that simply says that a user can have many comments. Now let\u2019s define the same relationship on the\u00a0Post\u00a0model. Open the\u00a0Post.php\u00a0file and include this method:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/ File: .\/app\/Post.php\r\n    public function comments()\r\n    {\r\n        return $this-&gt;hasMany(Comment::class);\r\n    }<\/pre>\n<p>Finally, we will include two methods in the\u00a0Comment\u00a0model to complete the second half of the relationships we defined in the\u00a0User\u00a0and\u00a0Post\u00a0models.<\/p>\n<p>Open the\u00a0app\/Comment.php\u00a0file and include these methods:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/ File: .\/app\/Comment.php\r\n    public function user()\r\n    {\r\n        return $this-&gt;belongsTo(User::class);\r\n    }\r\n\r\n    public function post()\r\n    {\r\n        return $this-&gt;belongsTo(Post::class);\r\n    }<\/pre>\n<p>Since we want to be able to mass assign data to specific fields of a comment instance during comment creation, we will include this array of permitted assignments in the\u00a0app\/Comment.php\u00a0file:<\/p>\n<pre class=\"lang:default decode:true \">protected $fillable = ['user_id', 'post_id', 'body'];<\/pre>\n<p>We can now run our database migration for our comments:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan migrate\r\nMigrating: 2019_01_30_042953_create_comments_table\r\nMigrated:  2019_01_30_042953_create_comments_table<\/pre>\n<p>CONFIGURING LARAVEL TO BROADCAST EVENTS USING PUSHER<br \/>\nWe already said that the comments will have a realtime functionality and we will be building this using Pusher, so we need to enable Laravel\u2019s event broadcasting feature.<\/p>\n<p>Open the\u00a0config\/app.php\u00a0file and uncomment the following line in the\u00a0providers\u00a0array:<\/p>\n<pre class=\"lang:default decode:true\">App\\Providers\\BroadcastServiceProvider,<\/pre>\n<p>Next, we need to configure the broadcast driver in the\u00a0.env\u00a0file. Replace from BROADCAST_DRIVER=log TO<\/p>\n<pre class=\"lang:default decode:true \">BROADCAST_DRIVER=pusher<\/pre>\n<p>Let\u2019s pull in the Pusher PHP SDK using composer:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ composer require pusher\/pusher-php-server\r\nUsing version ^3.3 for pusher\/pusher-php-server\r\n.\/composer.json has been updated\r\nLoading composer repositories with package information\r\nUpdating dependencies (including require-dev)\r\nPackage operations: 2 installs, 0 updates, 0 removals\r\n  - Installing paragonie\/sodium_compat (v1.8.1): Downloading (100%)         \r\n  - Installing pusher\/pusher-php-server (v3.3.1): Downloading (100%)         \r\nparagonie\/sodium_compat suggests installing ext-libsodium (PHP &lt; 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.)\r\nWriting lock file\r\nGenerating optimized autoload files\r\n&gt; Illuminate\\Foundation\\ComposerScripts::postAutoloadDump\r\n&gt; @php artisan package:discover --ansi\r\nDiscovered Package: beyondcode\/laravel-dump-server\r\nDiscovered Package: fideloper\/proxy\r\nDiscovered Package: laravel\/nexmo-notification-channel\r\nDiscovered Package: laravel\/slack-notification-channel\r\nDiscovered Package: laravel\/tinker\r\nDiscovered Package: nesbot\/carbon\r\nDiscovered Package: nunomaduro\/collision\r\nPackage manifest generated successfully.<\/pre>\n<p>CONFIGURING PUSHER<br \/>\nFor us to use Pusher in this application, it is a prerequisite that you have a Pusher account. You can create a free Pusher account\u00a0here\u00a0then login to your dashboard and create an app.<\/p>\n<p>Once you have created an app, we will use the app details to configure pusher in the\u00a0.env\u00a0file:<\/p>\n<pre class=\"lang:default decode:true \">    PUSHER_APP_ID=xxxxxx\r\n    PUSHER_APP_KEY=xxxxxxxxxxxxxxxxxxxx\r\n    PUSHER_APP_SECRET=xxxxxxxxxxxxxxxxxxxx\r\n    PUSHER_APP_CLUSTER=xx<\/pre>\n<span class=\"rcp-restricted-content-message\">SORRY, ONLY ADMIN CAN SHOW THIS!<\/span>\n<p>Update the Pusher keys with the app credentials provided for you under the\u00a0Keys\u00a0section on the\u00a0Overview\u00a0tab on the Pusher dashboard.<\/p>\n<p>BROADCASTING AN EVENT FOR WHEN A NEW COMMENT IS SENT<br \/>\nTo make the comment update realtime, we have to broadcast an event based on the comment creation activity. We will create a new event and call it\u00a0CommentSent. It is to be fired when there is a successful creation of a new comment.<\/p>\n<p>Run command in your terminal:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan make:event CommentSent\r\nEvent created successfully.<\/pre>\n<p>There will be a newly created file in the\u00a0app\\Events\u00a0directory, open the\u00a0CommentSent.php\u00a0file and ensure that it implements the\u00a0ShouldBroadcast\u00a0interface.<\/p>\n<p>Open and replace the file with the following code:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/ File: .\/app\/Events\/CommentSent.php\r\n    &lt;?php \r\n\r\n    namespace App\\Events;\r\n\r\n    use App\\Comment;\r\n    use App\\User;\r\n    use Illuminate\\Broadcasting\\Channel;\r\n    use Illuminate\\Queue\\SerializesModels;\r\n    use Illuminate\\Broadcasting\\PrivateChannel;\r\n    use Illuminate\\Broadcasting\\PresenceChannel;\r\n    use Illuminate\\Foundation\\Events\\Dispatchable;\r\n    use Illuminate\\Broadcasting\\InteractsWithSockets;\r\n    use Illuminate\\Contracts\\Broadcasting\\ShouldBroadcast;\r\n\r\n    class CommentSent implements ShouldBroadcast\r\n    {\r\n        use Dispatchable, InteractsWithSockets, SerializesModels;\r\n\r\n        public $user;\r\n\r\n        public $comment;\r\n\r\n        public function __construct(User $user, Comment $comment)\r\n        {\r\n            $this-&gt;user = $user;\r\n\r\n            $this-&gt;comment = $comment;\r\n        }\r\n\r\n        public function broadcastOn()\r\n        {\r\n            return new PrivateChannel('comment');\r\n        }\r\n    }<\/pre>\n<p>In the code above, we created two public properties,\u00a0user\u00a0and\u00a0comment, to hold the data that will be passed to the channel we are broadcasting on. We also created a private channel called\u00a0comment. We are using a private channel so that only authenticated clients can subscribe to the channel.<br \/>\nNOTE: Make sure &#8216;CommentSent&#8217; class implements &#8216;ShouldBroadcast&#8217; class. When I accidentally missing to implement it, the message can&#8217;t be broadcasted at real time properly. I need to refresh the page to see the incoming message on the another login.<\/p>\n<p>DEFINING THE ROUTES FOR HANDLING OPERATIONS ON A COMMENT<br \/>\nWe created a controller for the comment model earlier but we haven\u2019t defined the web routes that will redirect requests to be handled by that controller.<\/p>\n<p>Open the\u00a0routes\/web.php\u00a0file and include the code below:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/ File: .\/routes\/web.php\r\n    Route::get('\/{post}\/comments', 'CommentController@index');\r\n    Route::post('\/{post}\/comments', 'CommentController@store');<\/pre>\n<p>SETTING UP THE ACTION METHODS IN THE COMMENTCONTROLLER<br \/>\nWe need to include two methods in the\u00a0CommentController.php\u00a0file, these methods will be responsible for storing and retrieving methods. In the\u00a0store()\u00a0method, we will also be broadcasting an event when a new comment is created.<\/p>\n<p>Open the\u00a0CommentController.php\u00a0file and replace its contents with the code below:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/ File: .\/app\/Http\/Controllers\/CommentController.php\r\n    &lt;?php \r\n\r\n    namespace App\\Http\\Controllers;\r\n\r\n    use App\\Comment;\r\n    use App\\Events\\CommentSent;\r\n    use App\\Post;\r\n    use Illuminate\\Http\\Request;\r\n\r\n    class CommentController extends Controller\r\n    {\r\n        public function store(Post $post)\r\n        {\r\n            $this-&gt;validate(request(), [\r\n                'body' =&gt; 'required',\r\n            ]);\r\n\r\n            $user = auth()-&gt;user();\r\n\r\n            $comment = Comment::create([\r\n                'user_id' =&gt; $user-&gt;id,\r\n                'post_id' =&gt; $post-&gt;id,\r\n                'body' =&gt; request('body'),\r\n            ]);\r\n\r\n            broadcast(new CommentSent($user, $comment))-&gt;toOthers();\r\n\r\n            return ['status' =&gt; 'Message Sent!'];\r\n        }\r\n\r\n        public function index(Post $post)\r\n        {\r\n            return $post-&gt;comments()-&gt;with('user')-&gt;get();\r\n        }\r\n    }<\/pre>\n<p>In the\u00a0store\u00a0method above, we are validating then creating a new post comment. After the comment has been created, we broadcast the\u00a0CommentSent\u00a0event to other clients so they can update their comments list in realtime.<\/p>\n<p>In the\u00a0index\u00a0method we just return the comments belonging to a post along with the user that made the comment.<\/p>\n<p>ADDING A LAYER OF AUTHENTICATION<br \/>\nLet\u2019s add a layer of authentication that ensures that only authenticated users can listen on the private\u00a0comment channel we created.<\/p>\n<p>Add the following code to the\u00a0routes\/channels.php\u00a0file:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/ File: .\/routes\/channels.php\r\n    Broadcast::channel('comment', function ($user) {\r\n        return auth()-&gt;check();\r\n    });<\/pre>\n<p>ADDING COMMENTS TO THE FRONTEND<br \/>\nIn the second article of this series, we created the view for the single post landing page in the\u00a0single.blade.php\u00a0file, but we didn\u2019t add the comments functionality. We are going to add it now. We will be using Vue to build the comments for this application so the first thing we will do is to include Vue in the frontend of our application.<\/p>\n<p>Open the master layout template and include Vue to its\u00a0&lt;head&gt;\u00a0tag. Just before the\u00a0&lt;title&gt;\u00a0tag appears in the\u00a0master.blade.php\u00a0file, include this snippet:<\/p>\n<pre class=\"lang:default decode:true \">    &lt;!-- File: .\/resources\/views\/layouts\/master.blade.php --&gt;\r\n    &lt;meta name=\"csrf-token\" content=\"{{ csrf_token() }}\"&gt;\r\n    &lt;script src=\"{{ asset('js\/app.js') }}\" defer&gt;&lt;\/script&gt;<\/pre>\n<p>The\u00a0csrf_token()\u00a0is there so that users cannot forge requests in our application. All our requests will pick the randomly generated\u00a0csrf-token\u00a0and use that to make requests.<\/p>\n<p>Related:\u00a0<a href=\"https:\/\/blog.pusher.com\/csrf-laravel-verifycsrftoken\/\">CSRF in Laravel: how VerifyCsrfToken works and how to prevent attacks<\/a><\/p>\n<p>Now the next thing we want to do is update the\u00a0resources\/js\/app.js\u00a0file so that it includes a template for the comments view.<\/p>\n<p>Open the file and replace its contents with the code below:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/resources\/js\/app.js\r\n    require('.\/bootstrap');\r\n\r\n    import Vue          from 'vue'\r\n    ...\r\n    import Comments from '.\/components\/Comments'\r\n\r\n    Vue.use(VueRouter)\r\n\r\n    const router = new VueRouter({\r\n        ...\r\n    });\r\n\r\n    const app = new Vue({\r\n        el: '#app',\r\n        components: { Homepage, Comments },\r\n        router,\r\n    });<\/pre>\n<p>Above we imported the\u00a0Comment\u00a0component and then we added it to the list of components in the applications Vue instance.<\/p>\n<p>Now create a\u00a0Comments.vue\u00a0file in the\u00a0resources\/js\/components\u00a0directory. This is where all the code for our comment view will go. We will populate this file later on.<\/p>\n<p>INSTALLING PUSHER AND LARAVEL ECHO<br \/>\nFor us to be able to use Pusher and subscribe to events on the frontend, we need to pull in both Pusher and Laravel Echo. We will do so by running this command:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ npm install --save laravel-echo pusher-js\r\nnpm WARN rm not removing \/home\/teddy\/Documents\/works\/laravel\/laravel-cms\/node_modules\/.bin\/regjsparser as it wasn't installed by \/home\/teddy\/Documents\/works\/laravel\/laravel-cms\/node_modules\/regjsparser\r\nnpm WARN rm not removing \/home\/teddy\/Documents\/works\/laravel\/laravel-cms\/node_modules\/.bin\/json5 as it wasn't installed by \/home\/teddy\/Documents\/works\/laravel\/laravel-cms\/node_modules\/json5\r\nnpm WARN rm not removing \/home\/teddy\/Documents\/works\/laravel\/laravel-cms\/node_modules\/.bin\/jsesc as it wasn't installed by \/home\/teddy\/Documents\/works\/laravel\/laravel-cms\/node_modules\/jsesc\r\nnpm WARN rm not removing \/home\/teddy\/Documents\/works\/laravel\/laravel-cms\/node_modules\/.bin\/cssesc as it wasn't installed by \/home\/teddy\/Documents\/works\/laravel\/laravel-cms\/node_modules\/cssesc\r\n\r\n&gt; webpack-cli@3.2.1 postinstall \/home\/teddy\/Documents\/works\/laravel\/laravel-cms\/node_modules\/webpack-cli\r\n&gt; lightercollective\r\n\r\n\r\n     *** Thank you for using webpack-cli! ***\r\n\r\nPlease consider donating to our open collective\r\n     to help us maintain this package.\r\n\r\n  https:\/\/opencollective.com\/webpack\/donate\r\n\r\n                    ***\r\n\r\nnpm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.7 (node_modules\/fsevents):\r\nnpm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.7: wanted {\"os\":\"darwin\",\"arch\":\"any\"} (current: {\"os\":\"linux\",\"arch\":\"x64\"})\r\n\r\n+ laravel-echo@1.5.2\r\n+ pusher-js@4.3.1\r\nadded 87 packages from 71 contributors, removed 51 packages, updated 852 packages and audited 16870 packages in 12.86s\r\nfound 0 vulnerabilities<\/pre>\n<p><a href=\"https:\/\/github.com\/laravel\/echo\">Laravel Echo<\/a>\u00a0is a JavaScript library that makes it easy to subscribe to channels and listen for events broadcast by Laravel.<br \/>\nNow let\u2019s configure Laravel Echo to work in our application. In the\u00a0resources\/js\/bootstrap.js\u00a0file, find and uncomment this snippet of code:<\/p>\n<pre class=\"lang:default decode:true \">    \/\/resources\/js\/bootstrap.js\r\n    import Echo from 'laravel-echo'\r\n\r\n    window.Pusher = require('pusher-js');\r\n\r\n    window.Echo = new Echo({\r\n         broadcaster: 'pusher',\r\n         key: process.env.MIX_PUSHER_APP_KEY,\r\n         cluster: process.env.MIX_PUSHER_APP_CLUSTER,\r\n         encrypted: true\r\n    });<\/pre>\n<p>The\u00a0key\u00a0and\u00a0cluster\u00a0will pull the keys from your\u00a0.env\u00a0file so no need to enter them manually again.<br \/>\nNow let\u2019s import the\u00a0Comments\u00a0component into the\u00a0single.blade.php\u00a0file and pass along the required the props.<\/p>\n<p>Open the\u00a0single.blade.php\u00a0file and replace its contents with the code below:<\/p>\n<pre class=\"lang:default decode:true \">{{-- File: .\/resources\/views\/single.blade.php --}}\r\n@extends('layouts.master')\r\n\r\n@section('content')\r\n&lt;div class=\"container\"&gt;\r\n  &lt;div class=\"row\"&gt;\r\n    &lt;div class=\"col-lg-10 mx-auto\"&gt;\r\n      &lt;h3 class=\"mt-4\"&gt;{{ $post-&gt;title }} &lt;span class=\"lead\"&gt; by &lt;a href=\"#\"&gt; {{ $post-&gt;user-&gt;name }} &lt;\/a&gt;&lt;\/span&gt; &lt;\/h3&gt;\r\n      &lt;hr&gt;\r\n      &lt;p&gt;Posted {{ $post-&gt;created_at-&gt;diffForHumans() }} &lt;\/p&gt;\r\n      &lt;hr&gt;\r\n      &lt;img class=\"img-fluid rounded\" src=\" {!! !empty($post-&gt;image) ? '\/uploads\/posts\/' . $post-&gt;image :  'http:\/\/placehold.it\/750x300' !!} \" alt=\"\"&gt;\r\n      &lt;hr&gt;\r\n      &lt;div&gt;\r\n        &lt;p&gt;{{ $post-&gt;body }}&lt;\/p&gt;\r\n        &lt;hr&gt;\r\n        &lt;br&gt;\r\n      &lt;\/div&gt;\r\n\r\n      &lt;!--&lt;div class=\"card my-4\"&gt;\r\n        &lt;h5 class=\"card-header\"&gt;Leave a Comment:&lt;\/h5&gt;\r\n        &lt;div class=\"card-body\"&gt;\r\n          &lt;form&gt;\r\n            &lt;div class=\"form-group\"&gt;\r\n              &lt;textarea class=\"form-control\" rows=\"3\"&gt;&lt;\/textarea&gt;\r\n            &lt;\/div&gt;\r\n            &lt;button type=\"submit\" class=\"btn btn-primary\"&gt;Submit&lt;\/button&gt;\r\n          &lt;\/form&gt;\r\n        &lt;\/div&gt;\r\n      &lt;\/div&gt;--&gt;\r\n\r\n      @auth\r\n      &lt;Comments \r\n        :post-id = '@json($post-&gt;id)' \r\n        :user-name = '@json(auth()-&gt;user()-&gt;name)'&gt;\r\n      &lt;\/Comments&gt;\r\n      @endauth\r\n    &lt;\/div&gt;\r\n  &lt;\/div&gt;\r\n&lt;\/div&gt;\r\n@endsection<\/pre>\n<p>BUILDING THE COMMENTS VIEW<br \/>\nOpen the\u00a0Comments.vue\u00a0file and add the following markup template below:<\/p>\n<pre class=\"lang:default decode:true\">    &lt;!-- File: .\/resources\/js\/components\/Comments.vue --&gt;\r\n    &lt;template&gt;\r\n      &lt;div class=\"card my-4\"&gt;\r\n        &lt;h5 class=\"card-header\"&gt;Leave a Comment:&lt;\/h5&gt;\r\n        &lt;div class=\"card-body\"&gt;\r\n          &lt;form&gt;\r\n            &lt;div class=\"form-group\"&gt;\r\n              &lt;textarea ref=\"body\" class=\"form-control\" rows=\"3\"&gt;&lt;\/textarea&gt;\r\n            &lt;\/div&gt;\r\n            &lt;button type=\"submit\" @click.prevent=\"addComment\" class=\"btn btn-primary\"&gt;\r\n              Submit\r\n            &lt;\/button&gt;\r\n          &lt;\/form&gt;\r\n        &lt;\/div&gt;\r\n        &lt;p class=\"border p-3\" v-for=\"comment in comments\"&gt;\r\n           &lt;strong&gt;{{ comment.user.name }}&lt;\/strong&gt;: \r\n           &lt;span&gt;{{ comment.body }}&lt;\/span&gt;\r\n        &lt;\/p&gt;\r\n      &lt;\/div&gt;\r\n    &lt;\/template&gt;<\/pre>\n<p>Now, we\u2019ll add a script that defines two methods:<\/p>\n<p>fetchComments()\u00a0&#8211; this will fetch all the existing comments when the component is created.<br \/>\naddComment()\u00a0&#8211; this will add a new comment by hitting the backend server. It will also trigger a new event that will be broadcast so all clients receive them in realtime.<br \/>\nIn the same file, add the following below the closing\u00a0template\u00a0tag:<\/p>\n<pre class=\"lang:default decode:true \">&lt;!-- File: .\/resources\/js\/components\/Comments.vue --&gt;\r\n&lt;template&gt;\r\n    ...\r\n&lt;\/template&gt;\r\n&lt;script&gt;\r\nexport default {\r\n    props: {\r\n        userName: {\r\n            type: String,\r\n            required: true\r\n        },\r\n        postId: {\r\n            type: Number,\r\n            required: true\r\n        }\r\n    },\r\n    data() {\r\n        return {\r\n            comments: []\r\n        };\r\n    },\r\n    created() {\r\n        this.fetchComments();\r\n    },\r\n    methods: {\r\n        fetchComments() {\r\n            axios.get(\"\/\" + this.postId + \"\/comments\").then(response =&gt; {\r\n                this.comments = response.data;\r\n            });\r\n        },\r\n        addComment() {\r\n            let body = this.$refs.body.value;\r\n\r\n            axios.post(\"\/\" + this.postId + \"\/comments\", { body }).then(response =&gt; {\r\n                this.comments.push({\r\n                    user: {name: this.userName},\r\n                    body: this.$refs.body.value\r\n                });\r\n\r\n                this.$refs.body.value = '';\r\n            });\r\n        }\r\n    },\r\n}\r\n&lt;\/script&gt;<\/pre>\n<p>In the\u00a0created()\u00a0method above, we first made a call to the\u00a0fetchComments()\u00a0method, then we created a listener to the private\u00a0comment\u00a0channel using Laravel Echo. Once this listener is triggered, the\u00a0comments property is updated.<\/p>\n<p>TESTING THE APPLICATION<br \/>\nNow let\u2019s test the application to see if it is working as intended. Before running the application, we need to refresh our database so as to revert any changes. To do this, run the command below in your terminal:<\/p>\n<pre class=\"lang:default decode:true \">teddy@teddy:~\/Documents\/works\/laravel\/laravel-cms$ php artisan migrate:fresh --seed\r\nDropped all tables successfully.\r\nMigration table created successfully.\r\nMigrating: 2014_10_12_000000_create_users_table\r\nMigrated:  2014_10_12_000000_create_users_table\r\nMigrating: 2014_10_12_100000_create_password_resets_table\r\nMigrated:  2014_10_12_100000_create_password_resets_table\r\nMigrating: 2019_01_21_031440_create_roles_table\r\nMigrated:  2019_01_21_031440_create_roles_table\r\nMigrating: 2019_01_21_032646_create_role_user_table\r\nMigrated:  2019_01_21_032646_create_role_user_table\r\nMigrating: 2019_01_21_083457_create_posts_table\r\nMigrated:  2019_01_21_083457_create_posts_table\r\nMigrating: 2019_01_30_042953_create_comments_table\r\nMigrated:  2019_01_30_042953_create_comments_table\r\nSeeding: RoleTableSeeder\r\nSeeding: UserTableSeeder\r\nSeeding: PostTableSeeder\r\nDatabase seeding completed successfully.<\/pre>\n<p>Next, let\u2019s build the application so that all the changes will be compiled and included as a part of the JavaScript file. To do this, run the following command on your terminal:<\/p>\n<pre class=\"lang:default decode:true \">npm run dev<\/pre>\n<p>Finally, let\u2019s serve the application using this command:<\/p>\n<pre class=\"lang:default decode:true \">php artisan serve<\/pre>\n<p>To test that our application works visit the application URL\u00a0http:\/\/localhost:8000\u00a0on two separate browser windows, we will log in to our application on each of the windows as a different user.<\/p>\n<p>We will finally make a comment on the same post on each of the browser windows and check that it updates in realtime on the other window:<\/p>\n<p>IMPROVEMENTS:<br \/>\nCurrently the comments can only be displayed if the user is logged in. But I want to display them also if no user is logged in but it only read the comments and can&#8217;t add the comment. So modify resources\/views\/single.blade.php:<\/p>\n<pre class=\"lang:default decode:true \">      @auth\r\n      &lt;Comments \r\n        :post-id='@json($post-&gt;id)' \r\n        :user-name='@json(auth()-&gt;user()-&gt;name)'&gt;\r\n      &lt;\/Comments&gt;\r\n      @else\r\n      &lt;Comments \r\n        :post-id='@json($post-&gt;id)' \r\n        :user-name='@json(\"\")'&gt;\r\n      &lt;\/Comments&gt;\r\n      @endauth<\/pre>\n<p>Make user-name variable is empty string. Then modify resources\/js\/components\/Comments.vue on the template and the script:<\/p>\n<pre class=\"lang:default decode:true \">&lt;template&gt;\r\n    &lt;div class=\"card my-4\"&gt;\r\n        &lt;h5 class=\"card-header\" v-if=\"userName\"&gt;\r\n            Leave a comment:\r\n        &lt;\/h5&gt;\r\n        &lt;h5 class=\"card-header\" v-else&gt;\r\n            Comments: (&lt;a href=\"\/login\"&gt;Login&lt;\/a&gt; to add comment)\r\n        &lt;\/h5&gt;\r\n        &lt;div class=\"card-body\" v-if=\"userName\"&gt;\r\n            ...\r\n        &lt;\/div&gt;\r\n        &lt;p class=\"border p-3\" v-for=\"comment in comments\"&gt;\r\n            ...\r\n        &lt;\/p&gt;\r\n    &lt;\/div&gt;\r\n&lt;\/template&gt;<\/pre>\n<p>On the template, show the text input if &#8216;userName&#8217; is exist. Otherwise just show the login link. Then on the script, get the broadcast only if the &#8216;userName&#8217; is exist:<\/p>\n<pre class=\"lang:default decode:true \">    created() {\r\n        this.fetchComments();\r\n\r\n        if(this.userName){\r\n            Echo.private(\"comment\").listen(\"CommentSent\", e =&gt; {\r\n                ...\r\n            });\r\n        }\r\n    },<\/pre>\n<p>Anyway if the user not logged in, they can&#8217;t get the real time message!<\/p>\n<p>How to show the message counter?<br \/>\nModify the template and the script in resources\/js\/components\/Comments.vue. On the script, add a new computed variable &#8216;commentCount&#8217; in &#8216;data&#8217;. Why not in &#8216;props&#8217;? because the variables in &#8216;data&#8217; can be changed real time. not only on the page load.<\/p>\n<pre class=\"lang:default decode:true\">    data() {\r\n        return {\r\n            comments: [],\r\n            commentCount: 0\r\n        };\r\n    },\r\n    created() {\r\n        ...\r\n\r\n        if(this.userName){\r\n            Echo.private(\"comment\").listen(\"CommentSent\", e =&gt; {\r\n                this.comments.push({\r\n                    ...\r\n                });\r\n\r\n                this.commentCount = this.comments.length;\r\n            });\r\n        }\r\n    },\r\n    methods: {\r\n        fetchComments() {\r\n            axios.get(\"\/\" + this.postId + \"\/comments\").then(response =&gt; {\r\n                this.comments = response.data;\r\n                this.commentCount = response.data.length;\r\n            });\r\n        },\r\n        addComment() {\r\n            ...\r\n            axios.post(\"\/\" + this.postId + \"\/comments\", { body }).then(response =&gt; {\r\n                this.comments.push({\r\n                    ...\r\n                });\r\n\r\n                this.commentCount = this.comments.length;\r\n\r\n                this.$refs.body.value = '';\r\n            });\r\n        }\r\n    },<\/pre>\n<p>&#8216;commentCount&#8217; is calculated in &#8216;addComment&#8217; method when the new comment is inserted. Also in &#8216;fetchComments&#8217; when the page is loaded. To calculate it at real time, also put it in &#8216;created&#8217;. Then show the counter on the template:<\/p>\n<pre class=\"lang:default decode:true \">        &lt;h5 class=\"card-header\" v-if=\"userName\"&gt;\r\n            There are {{ commentCount }} comments. Leave a comment:\r\n        &lt;\/h5&gt;\r\n        &lt;h5 class=\"card-header\" v-else&gt;\r\n            There are {{ commentCount }} comments. &lt;a href=\"\/login\"&gt;Login&lt;\/a&gt; to add comment.\r\n        &lt;\/h5&gt;<\/pre>\n<p>So the message counter would appear on the post page (single page). But how to show the counter on the homepage?<br \/>\nSo modify resources\/views\/landing.blade.php to show the counter:<\/p>\n<pre class=\"lang:default decode:true \">        ...\r\n        &lt;div class=\"card-body\"&gt;\r\n          ...\r\n          &lt;a href=\"\/posts\/{{ $post-&gt;id }}\" class=\"btn btn-primary\"&gt;Read More &amp;rarr;&lt;\/a&gt;\r\n          &lt;a href=\"\/posts\/{{ $post-&gt;id }}#comments\" class=\"btn btn-primary\"&gt;Comments ({{ $post-&gt;comments()-&gt;count() }})&lt;\/a&gt;\r\n        &lt;\/div&gt;\r\n        ...<\/pre>\n<p>give the bookmark link (#) to jump straight to the comments section. Modify resources\/js\/components\/Comments.vue to add the bookmark &#8216;comments&#8217;:<\/p>\n<pre class=\"lang:default decode:true \">&lt;template&gt;\r\n    &lt;div id=\"comments\" class=\"card my-4\"&gt;\r\n    ...<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Ref: https:\/\/pusher.com\/tutorials\/cms-laravel-vue-part-1 Install Laravel Installer globally (It&#8217;d be better than &#8216;create project&#8217; and download from the laravel source): teddy@teddy:~\/Documents\/works\/laravel$ composer global require &#8220;laravel\/installer&#8221; Changed current directory to \/home\/teddy\/.composer .\/composer.json is not writable. teddy@teddy:~\/Documents\/works\/laravel$ sudo composer global require &#8220;laravel\/installer&#8221; [sudo] password for teddy: Changed current directory to \/home\/teddy\/.composer Do not run Composer as root\/super user! See &hellip; <a href=\"https:\/\/myprojects.advchaweb.com\/index.php\/2019\/01\/21\/create-laravel-cms\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Create Laravel CMS&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[82,83],"tags":[],"class_list":["post-3395","post","type-post","status-publish","format-standard","hentry","category-laravel-2","category-vue"],"_links":{"self":[{"href":"https:\/\/myprojects.advchaweb.com\/index.php\/wp-json\/wp\/v2\/posts\/3395","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/myprojects.advchaweb.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/myprojects.advchaweb.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/myprojects.advchaweb.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/myprojects.advchaweb.com\/index.php\/wp-json\/wp\/v2\/comments?post=3395"}],"version-history":[{"count":67,"href":"https:\/\/myprojects.advchaweb.com\/index.php\/wp-json\/wp\/v2\/posts\/3395\/revisions"}],"predecessor-version":[{"id":3481,"href":"https:\/\/myprojects.advchaweb.com\/index.php\/wp-json\/wp\/v2\/posts\/3395\/revisions\/3481"}],"wp:attachment":[{"href":"https:\/\/myprojects.advchaweb.com\/index.php\/wp-json\/wp\/v2\/media?parent=3395"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/myprojects.advchaweb.com\/index.php\/wp-json\/wp\/v2\/categories?post=3395"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/myprojects.advchaweb.com\/index.php\/wp-json\/wp\/v2\/tags?post=3395"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}