The go-to PHP IDE with extensive out-of-the-box support for Laravel and its ecosystem.

Laravel + Vue.js AdminPanel Generator

Last updated on by

Laravel + Vue.js AdminPanel Generator image

Laravel and Vue.js are often used together. With more tools on these technologies are released, here’s one of them – presenting to you Vue+Laravel Admin Panel Generator.

Disclaimer: I’m the founder and one of the developers of this tool, and also Laravel-only generator QuickAdminPanel, but the goal in this article is not only to present you the product, but explain what it generates, and how Vue + Laravel work together. Also, you will find an example project with source available on Github.

How does the generator work?

For those who prefer video, here’s a quick demo:

Now, let’s look at it with more details.

Step 1. You create your panel without coding, just adding menu items and fields.

Step 2. At any point, you can view the generated code, file by file.

Step 3. Then you download the code and install it – locally or on your remote server, with these commands:

composer install
php artisan key:generate
php artisan migrate --seed
php artisan passport:install

Of course, your .env file should be configured at that point.

And then, on the front-end:

npm install
npm run dev

Step 4. That’s it; you have your panel.

Step 5. The most important thing: you can change the code however you want, it’s pure Laravel+Vue, without our generator’s package as a dependency. That’s the main difference from packages like Voyager or Laravel Backpack (which are both excellent, by the way!).


What are we generating – structure of the project

After you download the project, you see something like this:

Generated Code: Back-end Laravel

Let’s first analyze the back-end Laravel part, which serves as API:

Here’s routes/api.php file:

Route::group(['prefix' => '/v1', 'middleware' => ['auth:api'], 'namespace' => 'Api\V1', 'as' => 'api.'], function () {
Route::post('change-password', 'ChangePasswordController@changePassword')->name('auth.change_password');
Route::apiResource('roles', 'RolesController');
Route::apiResource('users', 'UsersController');
Route::apiResource('companies', 'CompaniesController');
Route::apiResource('employees', 'EmployeesController');
});

You can see apiResource for every CRUD, and also one separate POST for changing the password.

Controllers are namespaces under Api/V1, so here’s our app/Http/Controllers/Api/V1/CompaniesController.php:

namespace App\Http\Controllers\Api\V1;
 
use App\Company;
use App\Http\Controllers\Controller;
use App\Http\Resources\Company as CompanyResource;
use App\Http\Requests\Admin\StoreCompaniesRequest;
use App\Http\Requests\Admin\UpdateCompaniesRequest;
use Illuminate\Http\Request;
 
class CompaniesController extends Controller
{
public function index()
{
return new CompanyResource(Company::with([])->get());
}
 
public function show($id)
{
$company = Company::with([])->findOrFail($id);
 
return new CompanyResource($company);
}
 
public function store(StoreCompaniesRequest $request)
{
$company = Company::create($request->all());
 
 
return (new CompanyResource($company))
->response()
->setStatusCode(201);
}
 
public function update(UpdateCompaniesRequest $request, $id)
{
$company = Company::findOrFail($id);
$company->update($request->all());
 
 
return (new CompanyResource($company))
->response()
->setStatusCode(202);
}
 
public function destroy($id)
{
$company = Company::findOrFail($id);
$company->delete();
 
return response(null, 204);
}
}

We have a typical resourceful Controller, with one exception – Resources classes, which have been available since Laravel 5.5.

In our case, every resource is a simple conversion to an array, here’s a file app/Http/Resources/Company.php

namespace App\Http\Resources;
 
use Illuminate\Http\Resources\Json\JsonResource;
 
class Company extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

But you can extend it, adding your logic on top – see more examples here and here.

Finally, Laravel Passport protects all the routes – when installing the project, you need to run this:

php artisan passport:install

As an overall back-end result, every Controller is responsible for that specific CRUD operations called to the API, from Vue.js front-end.

Generated Code: Front-end Vue.js

Now, let’s take a look at front-end part. The main file for this is resources/client/assets/js/app.js, where we initiate the Vue and some libraries:

// ...
window.Vue = require('vue')
Vue.prototype.$eventHub = new Vue()
 
import router from './routes'
import store from './store'
import Datatable from 'vue2-datatable-component'
import VueAWN from 'vue-awesome-notifications'
import vSelect from 'vue-select'
import datePicker from 'vue-bootstrap-datetimepicker'
import VueSweetalert2 from 'vue-sweetalert2'
import 'eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.css'
 
Vue.use(Datatable)
Vue.use(VueAWN, {
position: 'top-right'
})
Vue.use(datePicker)
Vue.use(VueSweetalert2)
 
Vue.component('back-buttton', require('./components/BackButton.vue'))
Vue.component('bootstrap-alert', require('./components/Alert.vue'))
Vue.component('event-hub', require('./components/EventHub.vue'))
Vue.component('vue-button-spinner', require('./components/VueButtonSpinner.vue'))
Vue.component('v-select', vSelect)
 
moment.updateLocale(window.app_locale, {
week: {
dow: 1
}
})
 
const app = new Vue({
data: {
relationships: {},
dpconfigDate: {
format: window.date_format_moment
},
dpconfigTime: {
format: window.time_format_moment
},
dpconfigDatetime: {
format: window.datetime_format_moment,
sideBySide: true
}
},
router,
store
}).$mount('#app')

Related: Laravel Sweetalert

Next, every CRUD has its own set of components:

For showing the data table, we’re using vue2-datatable-component – here’s full code of resources/clients/assets/components/cruds/Companies/Index.vue:

<template>
<section class="content-wrapper" style="min-height: 960px;">
<section class="content-header">
<h1>Companies</h1>
</section>
 
<section class="content">
<div class="row">
<div class="col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">List</h3>
</div>
 
<div class="box-body">
<div class="btn-group">
<router-link :to="{ name: xprops.route + '.create' }" class="btn btn-success btn-sm">
<i class="fa fa-plus"></i> Add new
</router-link>
<button type="button" class="btn btn-default btn-sm" @click="fetchData">
<i class="fa fa-refresh" :class="{'fa-spin': loading}"></i> Refresh
</button>
</div>
</div>
 
<div class="box-body">
<div class="row" v-if="loading">
<div class="col-xs-4 col-xs-offset-4">
<div class="alert text-center">
<i class="fa fa-spin fa-refresh"></i> Loading
</div>
</div>
</div>
 
<datatable
v-if="!loading"
:columns="columns"
:data="data"
:total="total"
:query="query"
:xprops="xprops"
/>
</div>
</div>
</div>
</div>
</section>
</section>
</template>
 
 
<script>
import { mapGetters, mapActions } from 'vuex'
import DatatableActions from '../../dtmodules/DatatableActions'
import DatatableSingle from '../../dtmodules/DatatableSingle'
import DatatableList from '../../dtmodules/DatatableList'
import DatatableCheckbox from '../../dtmodules/DatatableCheckbox'
 
export default {
data() {
return {
columns: [
{ title: '#', field: 'id', sortable: true, colStyle: 'width: 50px;' },
{ title: 'Name', field: 'name', sortable: true },
{ title: 'Description', field: 'description', sortable: true },
{ title: 'Actions', tdComp: DatatableActions, visible: true, thClass: 'text-right', tdClass: 'text-right', colStyle: 'width: 130px;' }
],
query: { sort: 'id', order: 'desc' },
xprops: {
module: 'CompaniesIndex',
route: 'companies'
}
}
},
created() {
this.$root.relationships = this.relationships
this.fetchData()
},
destroyed() {
this.resetState()
},
computed: {
...mapGetters('CompaniesIndex', ['data', 'total', 'loading', 'relationships']),
},
watch: {
query: {
handler(query) {
this.setQuery(query)
},
deep: true
}
},
methods: {
...mapActions('CompaniesIndex', ['fetchData', 'setQuery', 'resetState']),
}
}
</script>
 
 
<style scoped>
 
</style>

Quite a lot of code, isn’t it? Of course, it could be more straightforward, but we tried to follow the official documentation and best practices, generating code for the cases that could be extended for bigger projects.

Next, we can take a look at Create.vue:

<template>
<section class="content-wrapper" style="min-height: 960px;">
<section class="content-header">
<h1>Companies</h1>
</section>
 
<section class="content">
<div class="row">
<div class="col-xs-12">
<form @submit.prevent="submitForm">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Create</h3>
</div>
 
<div class="box-body">
<back-buttton></back-buttton>
</div>
 
<bootstrap-alert />
 
<div class="box-body">
<div class="form-group">
<label for="name">Name</label>
<input
type="text"
class="form-control"
name="name"
placeholder="Enter Name"
:value="item.name"
@input="updateName"
>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea
rows="3"
class="form-control"
name="description"
placeholder="Enter Description"
:value="item.description"
@input="updateDescription"
>
</textarea>
</div>
</div>
 
<div class="box-footer">
<vue-button-spinner
class="btn btn-primary btn-sm"
:isLoading="loading"
:disabled="loading"
>
Save
</vue-button-spinner>
</div>
</div>
</form>
</div>
</div>
</section>
</section>
</template>
 
 
<script>
import { mapGetters, mapActions } from 'vuex'
 
export default {
data() {
return {
// Code...
}
},
computed: {
...mapGetters('CompaniesSingle', ['item', 'loading'])
},
created() {
// Code ...
},
destroyed() {
this.resetState()
},
methods: {
...mapActions('CompaniesSingle', ['storeData', 'resetState', 'setName', 'setDescription']),
updateName(e) {
this.setName(e.target.value)
},
updateDescription(e) {
this.setDescription(e.target.value)
},
submitForm() {
this.storeData()
.then(() => {
this.$router.push({ name: 'companies.index' })
this.$eventHub.$emit('create-success')
})
.catch((error) => {
console.error(error)
})
}
}
}
</script>
 
 
<style scoped>
 
</style>

Edit and Show components for the CRUD are pretty similar, so won’t discuss them here.

In addition to that Vue code, there are many small details and helpers like Sweet Alert, Notifications, Datepickers, and setting/getting relationships data for the forms. I guess I will leave it for you to analyze.

Notice: The choice of Vue.js libraries is pretty subjective, and it was the most challenging part of the project – to choose the Vue libraries to trust. Ecosystem still lacks standards, or 100% trusted open-source – a lot of movement in the market, some libraries are better supported than others. So it’s always hard to guess, and the best libraries will probably change with time, or new ones will appear.


That’s the end of a quick overview of Vue+Laravel QuickAdminPanel, try it out here: https://vue.quickadminpanel.com

Finally, here’s the source of a demo-project with two CRUDs: Companies and Customers.

I hope our generator will not only save you time on writing code but also show you how Vue can work with Laravel. Our way of structuring this code is not the only way, and you can structure your code differently, but we tried our best to stick to standards.

PovilasKorop photo

Creator of Courses and Tutorials at Laravel Daily

Filed in:
Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

image
Tinkerwell

Enjoy coding and debugging in an editor designed for fast feedback and quick iterations. It's like a shell for your application – but with multi-line editing, code completion, and more.

Visit Tinkerwell
Curotec logo

Curotec

World class Laravel experts with GenAI dev skills. LATAM-based, embedded engineers that ship fast, communicate clearly, and elevate your product. No bloat, no BS.

Curotec
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $3200/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
Get expert guidance in a few days with a Laravel code review logo

Get expert guidance in a few days with a Laravel code review

Expert code review! Get clear, practical feedback from two Laravel devs with 10+ years of experience helping teams build better apps.

Get expert guidance in a few days with a Laravel code review
PhpStorm logo

PhpStorm

The go-to PHP IDE with extensive out-of-the-box support for Laravel and its ecosystem.

PhpStorm
Laravel Cloud logo

Laravel Cloud

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Cloud
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Harpoon: Next generation time tracking and invoicing logo

Harpoon: Next generation time tracking and invoicing

The next generation time-tracking and billing software that helps your agency plan and forecast a profitable future.

Harpoon: Next generation time tracking and invoicing
Lucky Media logo

Lucky Media

Get Lucky Now - the ideal choice for Laravel Development, with over a decade of experience!

Lucky Media
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Multi-tenant Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit

The latest

View all →
Bagisto Visual: Theme Framework with Visual Editor for Laravel E-commerce image

Bagisto Visual: Theme Framework with Visual Editor for Laravel E-commerce

Read article
Automate Laravel Herd Worktrees with This Claude Code Skill image

Automate Laravel Herd Worktrees with This Claude Code Skill

Read article
Laravel Boost v2.0 Released with Skills Support image

Laravel Boost v2.0 Released with Skills Support

Read article
Laravel Debugbar v4.0.0 is released image

Laravel Debugbar v4.0.0 is released

Read article
Radiance: Generate Deterministic Mesh Gradient Avatars in PHP image

Radiance: Generate Deterministic Mesh Gradient Avatars in PHP

Read article
Speeding Up Laravel News With Cloudflare image

Speeding Up Laravel News With Cloudflare

Read article