# Laravel

This guide is a demonstration of how official Laravel Admin (opens new window) can heavily facilitates Vuetify Admin integration within laravel backend for full top to bottom development experience. We will also made usage of server and client code generator commands for YAML driven development showcase. Simply quick and done !

REQUIREMENTS

SOURCE CODE

You will find complete source code of this tutorial in the main repo (opens new window).

# Installation

Simply init your project by this simple steps :

laravel new my-laravel-admin-app
cd my-laravel-admin-app
composer require okami101/laravel-admin
php artisan admin:install
1
2
3
4

Then follow wizard.

# Required and suggested packages

Following required packages will be installed :

The installer will suggest you to install :

IDE HELPER

If you have selected ide helper package, you may add this lines into your main composer.json file :

"scripts": {
    "post-update-cmd": [
        "Illuminate\Foundation\ComposerScripts::postUpdate",
        "@php artisan ide-helper:generate",
        "@php artisan ide-helper:meta"
    ]
}
1
2
3
4
5
6
7

# Provided features and generated code

Finally, the installer will integrate some additional boilerplate code to get basic features working within Vuetify Admin :

  • Modify some configs as cors for ready-to-go API SPA plugging.
  • Impersonation feature
  • User controller for admin users management.
  • Simple account controller for profile editing and password change.
  • Inject docker files with ready to use MySQL, phpMyAdmin, Nginx and Redis !

SPA auth & impersonation

If you choose to install Laravel Sanctum, the installer will inject both EnsureFrontendRequestsAreStateful as well as Impersonate middlewares into following Http Kernel file :

app/Http/Kernel.php











 
 






<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /* ... */
        'api' => [
            \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            \Okami101\LaravelAdmin\Http\Middleware\Impersonate::class,
            'throttle:60,1',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    /* ... */
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Note as Impersonate must be placed just right AFTER the EnsureFrontendRequestsAreStateful middleware in order to fetch initial main authenticated admin user.

# Vue CLI admin UI project

At the end of installation, a full ready Vue CLI Admin project will be installed inside admin sub folder (default) with all required dependencies by using this preset (opens new window).

UI ADMIN GENERATE COMMAND

You can still regenerate new admin UI without reuse full installer by using php artisan admin:ui.

DIRECTORY STRUCTURE

See this getting started section for more detail of what you get inside the admin folder.

# Usage

# Run backend API

# With docker

After the installation, don't forget to adapt your environment variables with those outputted by installer when finished as following :

APP_URL=http://localhost:8000
APP_TIMEZONE=UTC

SANCTUM_STATEFUL_DOMAINS=localhost:8080

DB_HOST=mysql
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=laravel

REDIS_HOST=redis
CACHE_DRIVER=redis
SESSION_DRIVER=redis

DOCKER_PUID=1000
DOCKER_PGID=1000
NGINX_HTTP_PORT=8000
PMA_PORT=9000
MYSQL_ROOT_PASSWORD=root
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Adapt SANCTUM_STATEFUL_DOMAINS to your local url of Vue CLI admin app.

SANCTUM

SANCTUM_STATEFUL_DOMAINS environnement variable is always needed even if admin app is on same API domain. It allows cookie auth sharing between local client app and API server. On production you may set the full domain. For example if your app will be accessible on https://my.company.com : SANCTUM_STATEFUL_DOMAINS=my.company.com.

DOCKER_PUID AND DOCKER_PGID

You need to set properly this IDs according to your local linux user IDs in order to prevent all boring permissions issues inside Laravel storage folder. In general the first created linux user is 1000 by default.

You can finally launch docker-compose up and take a pastis (opens new window). Don't forget to put docker-compose exec laravel before each next artisan command.

Then you have to prepare laravel installation with migration and all dummy data (if you have some) :

docker-compose exec laravel php artisan storage:link
docker-compose exec laravel php artisan migrate:fresh --seed
1
2

If not included on your dummy data, you may need to create your first user by docker-compose exec laravel php artisan admin:user admin@example.com. You will be prompted for the user name and password.

ADMIN URL

By default admin URL is configured at http://localhost:8080 (opens new window) which is default Vue CLI dev serve URL.
Don't forget to edit it on production. Just edit ADMIN_URL environment variable for that.

# Classic install

First don't forget to put SANCTUM_STATEFUL_DOMAINS=localhost:8080 inside .env, where localhost:8080 is the default local url of Vue CLI admin app.

Then you have to prepare laravel installation as usual with all migrations and seeders :

php artisan storage:link
php artisan migrate:fresh --seed
php artisan admin:user admin@example.com # only if no already dummy users from your seeders
php artisan serve
1
2
3
4

# Run Admin UI

You're finally ready to get up and running the Admin UI by cd admin && yarn serve --open. It will redirect you to this customizable login page :

login

After login you will be redirect to this nice static dashboard, which is entirely customizable at src/views/Dashboard.vue file :

dashboard

Customizable profile page that allow basic user editing and password change is also fully functional and available under top right user dropdown menu :

dashboard

Finally a users section that allows users management with direct aside edit is also provided, as well as possibility of impersonation.

users

# Generators

This package provides 2 specific crud generator commands. Use php artisan crud:make [MyNewResource] [options] with many as options as possible in order to scaffolding following files :

  • Model
  • Controller with all crud operations
  • Policy for authorization
  • Store and update requests for validation rules (empty by default, so don't forget to add your own rules)
  • Http Resource for API data transformer
  • Migration with all pre-generated fields (including foreign !)
  • Factory and seeder (empty by default)

In addition, api routes should be registered automatically at routes/api.php file in this place :

Route::apiResources([
    /** previous entities */
    'my_new_resource' => 'MyNewResourceController',
]);
1
2
3
4

HELP COMMAND

Launch php artisan crud:make --help for all options documentation.

IDE AUTOCOMPLETION

Use php artisan ide-helper:models after in order to have full model autocompletion !

OVERRIDES

Use --force option for overwrite existing files.

DATABASE RELATED FILES

For both generator commands, you may add -mfs options to generate full migration file with all pre-generated fields, in addition to factory and seeder files.

RELATIONSHIP

In case of model relation, even if foreign keys can be generated in migration file by foreign on schema, you must manually add related eloquent relation in you model.

VALIDATION

For server-side validation, you must manually add rules to generated store and update request files inside app/Http/Requests directory.

# Generate your first entity

You're now ready for development. Let's try the power of above generators for initiate our first entity on API side. We will create a new Book model with followed fields : isbn, category, title, description, author, price, commentable, formats and publication_date. We will also set title and description as translatable fields. So we can use next command :

php artisan crud:make Book \
  --schema="isbn:string:unique, category:string, title:json, description:json, summary:json, author:string, price:float, commentable:boolean, formats:json, publication_date:date" \
  --translatable="title, description, summary" --searchable="title, description, author" \
  --filterable="title, author, commentable" \
  --sortable="isbn, title, author, price, publication_date" \
  -mfs --force
1
2
3
4
5
6

JSON

As title and description are translatable, we'll use JSON for storing all localized labels keyed by locale code.

All necessary files are now generated :

Additional generated files : BookPolicy, StoreBook, UpdateBook, BookSeeder and BookFactory.

# Dummy data

You may needs to write some factory code for dummy data. Let's write it on next file :

database/factories/BookFactory.php

<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\Book;
use Faker\Generator as Faker;
use Okami101\LaravelAdmin\Faker\Provider\Html;

$factory->define(Book::class, function (Faker $faker) {
    $faker->addProvider(new Html($faker));

    return [
        'isbn' => $faker->isbn13,
        'title' => [
            'en' => ucfirst($faker->words($faker->numberBetween(1, 5), true)),
            'fr' => ucfirst($faker->words($faker->numberBetween(1, 5), true)),
        ],
        'category' => $faker->randomElement(['novel', 'comic', 'cook', 'economy', 'politics', 'history', 'fantasy', 'biography']),
        'description' => [
            'en' => $faker->paragraph(10),
            'fr' => $faker->paragraph(10),
        ],
        'summary' => [
            'en' => $faker->htmlParagraphs(10, 10, 10),
            'fr' => $faker->htmlParagraphs(10, 10, 10),
        ],
        'author' => $faker->name,
        'formats' => $faker->randomElements(
            ['pocket', 'paperback', 'pdf', 'epub', 'kindle'],
            $faker->numberBetween(1, 5)
        ),
        'price' => $faker->randomFloat(2, 10, 50),
        'commentable' => $faker->boolean(80),
        'publication_date' => $faker->dateTime,
    ];
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

Now let's add dummy generation on seeder :

database/seeds/BookSeeder.php

<?php

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class BookSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('books')->truncate();

        factory(\App\Book::class, 500)->create();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Finally we can migrate and seed dummy data :

php artisan migrate
php artisan db:seed --class BookSeeder
1
2

You're now ready to implement the admin UI !

# Generate CRUD UI

We can of course use the same way of previous tutorials for generate our CRUD book pages via guesser's code, but now you've been thinking can we generate UI side same as previously for API ?

Of course we can ! Vue CLI Plugin will install same separate npm scripts commands but for UI side. Let's try crud:make npm script. Go to admin sub folder and launch next crud:make command :

yarn crud:make books --locale="en" --name="Book | Books" --icon="mdi-book" --label="title" --translatable \
  --fields="isbn, category:select, title, description, summary, author, price:number, commentable:boolean, formats:array, publication_date:date" \
  --columns="isbn, title, author, commentable, price, publication_date" \
  --filterable="title, author, commentable" \
  --sortable="isbn, title, author, sortable, publication_date" \
  --lint
1
2
3
4
5
6

HELP COMMAND

Use yarn vue-cli-service help crud:make for all options documentation.

It will register a new books resource, add link to books on sidebar and generate all CRUD files.

To finish you may readapt some fields or inputs for better representation :

  • On list and show views :
    • Add currency format for all price fields.
    • Change summary of show view to rich text type.
    • Add localized enums for category and formats.
    • Precise select on formats on show view for use localized enums (as array field don't use it by default).
  • On form views :
    • Add multiline prop on form for description in order to use textarea.
    • Use rich text input for summary (use TinyMCE editor).
    • Use multiple select for formats instead of input array.

Here the final code templates of all locales and book CRUD views, all above manual modifications are highlighted.

# YAML

For even more auto generation power and reusability, a direct resources yaml file descriptor can be used via php artisan crud:yaml my-new-resource.yml [options] for API and yarn crud:yaml my-new-resource.yml [options] for UI. You can also directly provide a directory which contains all necessary YAML resource descriptor files as needed.

Use name option in order to import only targeted resource.

# API & UI resources development

The first step is to create a YAML resource descriptor file that must validate specific JSON schema, which will be consumed by API and UI CRUD commands that will generate all basic working code, instead of creating all boring stuff all by hand.

In this tutorial we will use existing YAML files as sample. Take one of this YAML files descriptors (opens new window) according to your preferred locale and put it inside admin/generators folder (or anywhere you want). This file contains 2 resources :

  • Monsters : sample for show all different type of fields.
  • Child monsters : essentially for resource relationship purpose.

# API generator commands

DOCKER

For all next artisan commands, don't forget to add docker-compose exec laravel before.

Finally it's time to generate our basic CRUD boilerplate code.

First begin with server-side commands by using php artisan crud:yaml admin/generators/monsters.en.yml -mfs. That will generate all API based backend files as well as registering all crud resource API routes. See laravel specific section for all detail of what's going on.

# Add specific model relations

Foreign DB keys are already done by migration, but you need to manually add all needed eloquent relationship for each model.

app/Monster.php

public function children()
{
    return $this->hasMany(MonsterChild::class);
}
1
2
3
4

app/MonsterChild.php

public function monster()
{
    return $this->belongsTo(Monster::class);
}
1
2
3
4

# Additional dummy data

The next step is to write you seed data. Use generated factory and seeder for that. For get quicker start, pick this files from tutorial source code :

USER SEEDER

Take this user seeder file (opens new window) in order to not have to create your admin each time you reset the database.

FILE SAMPLES

For proper media files generation, you may need to download this sample media directory and copy this main DatabaseSeeder file (opens new window).

COMPOSER AUTOLOAD

Don't forget to launch composer dump-autoload if you don't pass via php artisan make:seed command.

Then generate all DB with dummy data by php artisan migrate:fresh --seed.

# Server-side data validation

We will not use them for this tutorial, but for real app it's heavily recommended to write your server-side validations on both store and update form requests for each resource (generated inside app/Http/Requests).

# UI generator commands

Note at this above commands generate only API side code. For UI side go to admin folder and launch yarn crud:yaml generators/monsters.en.yml --locale en --lint (for english sample). This will generate all CRUD pages for each entity inside src/resources with full searchable data table list, show, create and edit forms.

In short, only factory, seed data, validation rules and model eloquent relation on server-side has been written by hand.

Now you can run all backend API as well as admin UI and see the result !

Example of generated list of monsters page :

list

The show view :

show

And finally the form view :

form

# Internal services and traits included

# Specific resource base

For API resource definition, you may use Eloquent Resources (opens new window) that extends Okami101\LaravelAdmin\Http\Resources\BaseResource in case you need media or translatable fields support within Vuetify Admin.

This class will do 2 things :

  • Automatically get only the target locale for each translatable field.
  • Fetch some file info (id, title, url,...) with existing thumbnails for each media files attached to the model.

# Available model traits

This package includes some third party request related model traits in order to reduce controller code boilerplate.

# RequestMediaTrait

This trait will import InteractsWithMedia from Spatie MediaLibrary (opens new window) with additional of request file save management.
This trait will be responsible for taking all valid request files that correspond to a valid collection name, as well as delete asked media for given collection.

Let's take this model media registration :

public function registerMediaCollections(): void
{
    $this->addMediaCollection('logo')->singleFile();
    $this->addMediaCollection('local');
}
1
2
3
4
5

This trait will automatically search for all logo and local for uploaded file.
For media deletion, he looks for logo_delete and local_delete with valid media ids (can be array or single value).
The Vuetify Admin file upload component va-file-input will take care of all of that.

# Search filter

If you want use global search in Vuetify Admin Iterator, use Okami101\LaravelAdmin\Filters\SearchFilter with all searchable fields inside allowedFilters method of Spatie Query Builder (opens new window) as next :




 




return new BookCollection(
  QueryBuilder::for(Book::class)
      ->allowedFilters([
          AllowedFilter::custom('q', new SearchFilter(['isbn', 'title', 'description'])),
      ->exportOrPaginate()
    );
}
1
2
3
4
5
6
7

TIP

Use exportOrPaginate specific macro after end of each QueryBuilder in order to add both export and pagination with only one line of code. It will apply pagination only in case of perPage detection on request query string. In all cases, filtering and sorting are kept.

# Html Faker provider

You can use Okami101\LaravelAdmin\Faker\Provider\Html for easy html generation your factories :






 








 
 





use App\Book;
use Faker\Generator as Faker;
use Okami101\LaravelAdmin\Faker\Provider\Html;

$factory->define(Book::class, function (Faker $faker) {
    $faker->addProvider(new Html($faker));

    return [
        'isbn' => $faker->isbn13,
        'description' => [
            'en' => $faker->paragraph(10),
            'fr' => $faker->paragraph(10),
        ],
        'summary' => [
            'en' => $faker->htmlParagraphs(10, 10, 10),
            'fr' => $faker->htmlParagraphs(10, 10, 10),
        ],
        //
    ];
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20