This tutorial will show you how to create a simple crud application using Laravel 5.7, VueJS, and AdminLTE . I assume that you already know how to install Laravel 5 on your system. If you don’t, please refer to the official website. If you have laravel 5 already in your system. Then we’re good to go.
Environment and Layout Preparations
Install NPM Dependencies
The first thing we need to do is to install the node package manager dependencies. Open your terminal command, go to your app directory, and type the command :
1 |
npm install |
Install AdminLTE
For our backend purpose, we will use AdminLTE for the theme. Install it by this command:
1 |
npm install admin-lte@v3.0.0-alpha.2 --save |
Enable Laravel Login and Register
To enable registration and login, create the auth from the Laravel system:
1 |
php artisan make:auth |
Click Register on the upper right corner, and register your username and password.
Migrate your database to create the users table :
1 |
php artisan migrate |
Master Layout AdminLTE
Create a new file as our master layout. Master.blade.php inside resource/views/layouts folder. And use the starter AdminLTE3 :
Create Master Layout
Add your own logo.png and profile.png and put them inside /public/img folder.
master.blade.php
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
<!DOCTYPE html> <!-- This is a starter template page. Use this page to start your new project from scratch. This page gets rid of all links and provides the needed markup only. --> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>AdminLTE 3 | Starter</title> <link rel="stylesheet" href="/css/app.css"> </head> <body class="hold-transition sidebar-mini"> <div class="wrapper" id="app"> <!-- Navbar --> <nav class="main-header navbar navbar-expand bg-white navbar-light border-bottom"> <!-- Left navbar links --> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link" data-widget="pushmenu" href="#"><i class="fa fa-bars"></i></a> </li> </ul> <!-- SEARCH FORM --> <form class="form-inline ml-3"> <div class="input-group input-group-sm"> <input class="form-control form-control-navbar" type="search" placeholder="Search" aria-label="Search"> <div class="input-group-append"> <button class="btn btn-navbar" type="submit"> <i class="fa fa-search"></i> </button> </div> </div> </form> </nav> <!-- /.navbar --> <!-- Main Sidebar Container --> <aside class="main-sidebar sidebar-dark-primary elevation-4"> <!-- Brand Logo --> <a href="index3.html" class="brand-link"> <img src="./img/logo.png" alt="LaraVue Logo" class="brand-image img-circle elevation-3" style="opacity: .8"> <span class="brand-text font-weight-light">LaraVue</span> </a> <!-- Sidebar --> <div class="sidebar"> <!-- Sidebar user panel (optional) --> <div class="user-panel mt-3 pb-3 mb-3 d-flex"> <div class="image"> <img src="./img/profile.png" class="img-circle elevation-2" alt="User Image"> </div> <div class="info"> <a href="#" class="d-block"> {{ Auth::user()->name }} </a> </div> </div> <!-- Sidebar Menu --> <nav class="mt-2"> <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false"> <!-- Add icons to the links using the .nav-icon class with font-awesome or any other icon font library --> <li class="nav-item"> <a href="#" class="nav-link"> <i class="nav-icon fas fa-tachometer-alt"></i> <p> Dashboard </p> </a> </li> <li class="nav-item has-treeview"> <a href="#" class="nav-link active"> <i class="nav-icon fas fa-cogs"></i> <p> Management <i class="right fa fa-angle-left"></i> </p> </a> <ul class="nav nav-treeview"> <li class="nav-item"> <router-link to="/users" class="nav-link"> <i class="fas fa-user nav-icon"></i> <p>Users</p> </router-link> </li> <li class="nav-item"> <a href="#" class="nav-link"> <i class="fa fa-circle-o nav-icon"></i> <p>Inactive Page</p> </a> </li> </ul> </li> <li class="nav-item"> <a href="#" class="nav-link"> <i class="nav-icon fas fa-user"></i> <p> Profile </p> </a> </li> <li class="nav-item"> <a class="nav-link" href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();"> <i class="nav-icon fa fa-power-off"></i> <p> {{ __('Logout') }} </p> </a> <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;"> @csrf </form> </li> </ul> </nav> <!-- /.sidebar-menu --> </div> <!-- /.sidebar --> </aside> <!-- Content Wrapper. Contains page content --> <div class="content-wrapper"> <!-- Main content --> <div class="content"> <div class="container-fluid"> </div><!-- /.container-fluid --> </div> <!-- /.content --> </div> <!-- /.content-wrapper --> <!-- Main Footer --> <footer class="main-footer"> <!-- To the right --> <div class="float-right d-none d-sm-inline"> Anything you want </div> <!-- Default to the left --> <strong>Copyright © 2014-2018 <a href="https://adminlte.io">AdminLTE.io</a>.</strong> All rights reserved. </footer> </div> <script src="/js/app.js"></script> </body> </html> |
Modify home.blade.php to extends the layouts master
1 |
@extends('layouts.master') |
Modify bootstrap.js
1 2 3 4 5 6 7 |
try { window.Popper = require('popper.js').default; window.$ = window.jQuery = require('jquery'); require('bootstrap'); require('admin-lte'); } catch (e) {} |
Add adminlte to app.scss
Open resources/sass/app.scss and add the path from node_modules folder to adminlte:
1 |
@import '~admin-lte/dist/css/adminlte.css'; |
Run the npm from the terminal :
1 |
npm run watch |
If you encounter any vue mismatch error, run the following command :
1 |
npm update vue |
Installing Font Awesome
1 |
npm install @fortawesome/fontawesome-free |
Linking fontawesome to app.scss :
1 2 3 4 5 6 7 8 9 10 |
// Variables @import 'variables'; $fa-font-path: "../webfonts"; // Bootstrap @import '~bootstrap/scss/bootstrap'; @import '~admin-lte/dist/css/adminlte.css'; @import '~@fortawesome/fontawesome-free/scss/fontawesome.scss'; @import '~@fortawesome/fontawesome-free/scss/solid.scss'; @import '~@fortawesome/fontawesome-free/scss/brands.scss'; |
Run php artisan serve and login:
Installing Vue Router
Next up we will install the Vue Router :
1 |
npm install vue-router@3.0.2 |
When the installation has finished, open js/app.js and add the routes and router and the Profile and Dashboard components :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
require('./bootstrap'); window.Vue = require('vue'); import VueRouter from 'vue-router' Vue.use(VueRouter) let routes = [ { path: '/dashboard', component: require('./components/Dashboard.vue').default }, { path: '/profile', component: require('./components/Profile.vue').default } ] const router = new VueRouter({ mode: 'history', routes // short for `routes: routes` }) Vue.component('example-component', require('./components/ExampleComponent.vue').default); const app = new Vue({ el: '#app', router }); |
Create Profile.vue and Dashboard.vue component inside resources/js/components :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<template> <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card card-default"> <div class="card-header">Profile Component</div> <div class="card-body"> I'm a profile component. </div> </div> </div> </div> </div> </template> <script> export default { mounted() { console.log('Component mounted.') } } </script> |
Add <router-view> to our master template at the main-content section :
1 2 3 4 5 6 |
<!-- Main content --> <div class="content"> <div class="container-fluid"> <router-view></router-view> </div><!-- /.container-fluid --> </div> |
Update the href link
Next up is to update our href link.
1 2 3 4 5 6 7 8 |
<li class="nav-item"> <router-link to="/profile" class="nav-link"> <i class="nav-icon fas fa-user"></i> <p> Profile </p> </router-link> </li> |
Run npm run watch, try to click the dashboard and profile url.
Detecting Active Menu
To avoid 404 not found error for every path, let’s add a new route for routes/web.php:
1 |
Route::get('{path}',"HomeController@index")->where('path','([A-z\d-\/_.]+)?'); |
Modify master.blade.php to delete the active class on Management menu.
1 2 3 4 5 6 7 |
<a href="#" class="nav-link"> <i class="nav-icon fas fa-cogs"></i> <p> Management <i class="right fa fa-angle-left"></i> </p> </a> |
If we inspect element, there’s a class whenever we click the active menu. The router-link-exact-active menu. Add this class styling to resources/sass/app.scss :
1 2 3 4 |
.router-link-exact-active { background-color: #3f51b5; color: #fff !important; } |
Customizing User Table
Add Attributes to users table
Attribute type, bio, photo to users table migration :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public function up() { Schema::create('users', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->string('type')->default('user'); $table->mediumText('bio')->nullable(); $table->string('photo')->default('profile.png')->nullable(); $table->rememberToken(); $table->timestamps(); }); } |
Run the migration
1 |
php artisan migrate:fresh |
User Management CRUD
User Component
Now we are getting closer into the main topic of this tutorial, the CRUD on users. First of all, we will create the user component.
Create User Component
Create the users.vue component inside resources/js/components folder :
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
<template> <div class="container"> <div class="row mt-5"> <div class="col-md-12"> <div class="card"> <div class="card-header"> <h3 class="card-title">Users Table</h3> <div class="card-tools"> <button class="btn btn-success" data-toggle="modal" data-target="#addUser"> Add User <i class="fas fa-user-plus fa-fw"></i> </button> </div> </div> <!-- /.card-header --> <div class="card-body table-responsive p-0"> <table class="table table-hover"> <tr> <th>ID</th> <th>Name</th> <th>Email</th> <th>Type</th> <th>Action</th> </tr> <tr> <td>183</td> <td>John Doe</td> <td>11-7-2014</td> <td><span class="tag tag-success">Approved</span></td> <td> <a href=""> <i class="fa fa-edit"></i> </a> | <a href=""> <i class="fa fa-trash"></i> </a> </td> </tr> </table> </div> <!-- /.card-body --> </div> <!-- /.card --> </div> </div><!-- /.row --> <!-- Modal --> <div class="modal fade" id="addUser" tabindex="-1" role="dialog" aria-labelledby="addUserLabel" aria-hidden="true"> <div class="modal-dialog modal-dialog-centered" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="addUserLabel">Add User</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> ... </div> <div class="modal-footer"> <button type="button" class="btn btn-danger" data-dismiss="modal">Close</button> <button type="button" class="btn btn-primary">Create User</button> </div> </div> </div> </div> </div> </template> <script> export default { mounted() { console.log('Component mounted.') } } </script> |
Users.vue component already has modal. It will pop up whenever we click the Add User button.
Register the users component inside app.js :
1 2 3 4 5 |
let routes = [ { path: '/dashboard', component: require('./components/Dashboard.vue').default }, { path: '/profile', component: require('./components/Profile.vue').default }, { path: '/users', component: require('./components/Users.vue').default } ] |
Using V-Form
V-Form is a simple way to handle laravel back-end validation in Vue 2. Install it by typing this command :
1 |
npm i axios vform@1.0.0 |
Import the form to app.js
1 2 3 4 5 |
import { Form, HasError, AlertError } from 'vform'; window.Form = Form; Vue.component(HasError.name, HasError) Vue.component(AlertError.name, AlertError) |
Bootstrap Modal and Form
Next, modify Users.vue to add the data method and HTML form for the modal :
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
<template> <div class="container"> <div class="row mt-5"> <div class="col-md-12"> <div class="card"> <div class="card-header"> <h3 class="card-title">Users Table</h3> <div class="card-tools"> <button class="btn btn-success" data-toggle="modal" data-target="#addUser">Add User <i class="fas fa-user-plus fa-fw"></i></button> </div> </div> <!-- /.card-header --> <div class="card-body table-responsive p-0"> <table class="table table-hover"> <tr> <th>ID</th> <th>Name</th> <th>Email</th> <th>Type</th> <th>Action</th> </tr> <tr> <td>183</td> <td>John Doe</td> <td>11-7-2014</td> <td><span class="tag tag-success">Approved</span></td> <td> <a href=""> <i class="fa fa-edit"></i> </a> | <a href=""> <i class="fa fa-trash"></i> </a> </td> </tr> </table> </div> <!-- /.card-body --> </div> <!-- /.card --> </div> </div><!-- /.row --> <!-- Modal --> <div class="modal fade" id="addUser" tabindex="-1" role="dialog" aria-labelledby="addUserLabel" aria-hidden="true"> <div class="modal-dialog modal-dialog-centered" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="addUserLabel">Add User</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <form @submit.prevent="createUser"> <div class="modal-body"> <div class="form-group"> <input v-model="form.name" type="text" name="name" class="form-control" placeholder="Name" :class="{ 'is-invalid':form.errors.has('name') }"> <has-error :form="form" field="name"></has-error> </div> <div class="form-group"> <input v-model="form.email" type="email" name="email" class="form-control" placeholder="Email Address" :class="{ 'is-invalid':form.errors.has('email') }"> <has-error :form="form" field="email"></has-error> </div> <div class="form-group"> <input v-model="form.bio" type="text" name="bio" class="form-control" placeholder="Bio" :class="{ 'is-invalid':form.errors.has('bio') }"> <has-error :form="form" field="bio"></has-error> </div> <div class="form-group"> <select name="type" v-model="form.type" id="type" class="form-control" :class="{ 'is-invalid':form.errors.has('type') }"> <option value="">Select User Role</option> <option value="admin">Admin</option> <option value="user">User</option> <option value="author">Author</option> </select> <has-error :form="form" field="type"></has-error> </div> <div class="form-group"> <input v-model="form.password" type="password" name="password" class="form-control" placeholder="Password" :class="{ 'is-invalid':form.errors.has('password') }"> <has-error :form="form" field="password"></has-error> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-danger" data-dismiss="modal">Close</button> <button type="button" class="btn btn-primary">Create User</button> </div> </form> </div> </div> </div> </div> </template> <script> export default { data() { return { form: new Form({ name: '', email: '', password: '', type: '', bio: '', photo:'' }) } }, methods: { createUser(){ this.form.post('api/user'); } }, mounted() { console.log('Component mounted.') } } </script> |
API Resource Controller
First, let’s create a UserController inside API folder :
1 |
php artisan make:controller API/UserController --api |
Add the api resources to routes/api.php
1 |
Route::apiResources(['user' => 'API\UserController']); |
Insert User Data To Database
First, we need to enable the mass insertion on User model :
1 2 3 |
protected $fillable = [ 'name', 'email', 'password','type','bio','photo' ]; |
Store the User
Open the API\UserController and modify the use and store method :
1 2 3 4 |
use Illuminate\Http\Request; use App\Http\Controllers\Controller; use App\User; use Illuminate\Support\Facades\Hash; |
1 2 3 4 5 6 7 8 9 10 11 |
public function store(Request $request) { return User::create([ 'name' => $request['name'], 'email' => $request['email'], 'type' => $request['type'], 'bio' => $request['bio'], 'photo' => $request['photo'], 'password' => Hash::make($request['password']) ]); } |
Now fill in the form and push Create User :
Check your database, the new user will be available :
V-Form Server Validation
To create validate is quite straightforward. You need to add the validation rule on the controller method :
1 2 3 4 5 6 7 |
public function store(Request $request) { $this->validate($request,[ 'name' => 'required|string', 'email'=> 'required|string|email|max:255|unique:users', 'password' => 'required|string|min:6' ]); |
Displaying Users using /GET
Install vue-progressbar and sweetalert2
1 2 |
npm install vue-progressbar@0.7.5 --save npm install sweetalert2@8.2.6 --save |
Add the public function index() inside UserController :
1 2 3 4 |
public function index() { return User::latest()->paginate(10); } |
Modify the Users.vue component to add the loadUsers method when the page has been loaded. Add a users variable as an empty object.
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 37 38 39 40 41 42 43 44 45 46 47 |
<script> export default { data() { return { users: {}, form: new Form({ name: '', email: '', password: '', type: '', bio: '', photo:'' }) } }, methods: { loadUsers(){ axios.get("api/user") .then(({data}) => (this.users = data.data)); }, createUser(){ this.$Progress.start() this.form.post('api/user') .then(()=>{ Fire.$emit('AfterCreate'); $('#addUser').modal('hide'); toast.fire({ type: 'success', title: 'User Created successfully' }) this.$Progress.finish(); }) .catch(()=>{ this.$Progress.fail(); }) }, }, mounted() { this.loadUsers(); Fire.$on('AfterCreate', () => { this.loadUsers(); }) // setInterval(() => this.loadUsers(), 3000) } } </script> |
Loop through users by modifying the table body :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<tr v-for="user in users" :key="user.id"> <td>{{ user.id }}</td> <td>{{ user.name }}</td> <td>{{ user.email }}</td> <td>{{ user.type }}</td> <td>{{ user.created_at }}</td> <td> <a href=""> <i class="fa fa-edit"></i> </a> | <a href=""> <i class="fa fa-trash"></i> </a> </td> </tr> |
Run the app :
Vue Filter and Moment.JS
Vue has a filter method to return output for the attribute as we want. For example, we want to capitalize first letter of user type.
Open app.js and add these lines :
1 2 3 4 5 |
Vue.filter('capitalize', function (value) { if (!value) return '' value = value.toString() return value.charAt(0).toUpperCase() + value.slice(1) }) |
Use this filter to Users component :
1 |
<td>{{ user.type | capitalize}}</td> |
Next for the Registered At column, let’s create this filter :
1 2 3 |
Vue.filter('theDate', function (created) { return moment(created).format('MMMM Do, YYYY'); }) |
However, we need to install moment.js and sweet alert2 :
1 |
npm install moment@2.24.0 --save |
Use this filter to Users component :
1 |
<td>{{ user.created_at | theDate }}</td> |
DELETE User Route
We will use sweetalert2 to delete a user. Previously, we need to add event when the trash icon is clicked is Users.vue component :
1 2 3 |
<a href="#"> <i class="fa fa-trash" @click="deleteUser(user.id)"></i> </a> |
Add delete user method :
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 37 38 |
deleteUser(id){ this.$Progress.start() swal.fire({ title: 'Are you sure?', text: "You won't be able to revert this!", type: 'warning', showCancelButton: true, confirmButtonColor: '#3085d6', cancelButtonColor: '#d33', confirmButtonText: 'Yes, delete it!' }).then((result) => { //send request to the server if (result.value) { this.form.delete('api/user/'+id) .then(()=>{ Fire.$emit('AfterCreate'); swal.fire( 'Deleted!', 'Your file has been deleted.', 'success' ) this.$Progress.finish(); }) .catch((data)=>{ console.log(data); swal( 'Failed!', 'There was something wrong.', 'warning' ) this.$Progress.fail(); }) } else { this.$Progress.fail(); } }) } |
Modify UserController and add the destroy method :
1 2 3 4 5 6 7 8 9 |
public function destroy($id) { $user = User::findOrFail($id); // delete process $user->delete(); return ['message'=>'User has deleted', 'data_user'=>$user]; } |
Try to click the trash icon :
Users page not show when I insert (line 60)
It is blank just showing master page.
tombol create modal tidak berjalan
kenapa y? di console juga g ada error
id nya sudah sesuai?
Apakah ada github repo nya?
Saya ada error,
Font Awesome nya gak keluar.
Thanks