Laravel & Vue CRUD Single Page Application (SPA) Tutorial
In this tutorial, using Laravel and Vue.Js, we are going to create a single page application. We will learn how to create, read, update and delete using Vue.Js frontend and Laravel API backend.
Update: I’ve written a new article on SPA. Have a look Laravel SPA with Vue 3, Auth (Sanctum), CRUD Example.
Note: Last tested on Laravel 6.x. This artcle will work on latest version of Laravel too. But you have to change a few things.
Table of Contents
- Install Laravel and NPM Dependencies
- Create Migration, Model and Controller
- Define Laravel Routes
- Create Vue App
- Create Vue Components
- Define Vue Routes
- Import All to app.js
- The Output
- Issue: ‘axios’ of undefined
Install Laravel and NPM Dependencies
Each Laravel project needs this thing. That’s why I have written an article on this topic. Please see this part from here: Install Laravel and Basic Configurations.
Now, we need to install vue-router and vue-axios. vue-axios will be used for calling Laravel API. Let’s install these:
npm install vue-router vue-axios --save
After installing all dependencies run this command:
npm run watch
This npm run watch
command will listen for file changes and will compile assets instantly. You can also run this command npm run dev
. It won’t listen for file changes.
Create Migration, Model and Controller
We are going to create Book model, migration and controller. We will add, read, update, delete book. Run this artisan command to create these 3 at once:
php artisan make:model Book -mcr
Now open create_books_table.php migration file from database>migrations and replace up() function with this:
public function up()
{
Schema::create('books', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('author');
$table->timestamps();
});
}
Migrate the database using the following command:
php artisan migrate
Open Book.php model from app folder and paste this code:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Book extends Model
{
protected $fillable = ['name', 'author'];
}
We need to define the index, add, edit, delete methods in BookController file. Open BookController.php from app>Http>Controllers folder and paste this code:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Book;
class BookController extends Controller
{
// all books
public function index()
{
$books = Book::all()->toArray();
return array_reverse($books);
}
// add book
public function add(Request $request)
{
$book = new Book([
'name' => $request->input('name'),
'author' => $request->input('author')
]);
$book->save();
return response()->json('The book successfully added');
}
// edit book
public function edit($id)
{
$book = Book::find($id);
return response()->json($book);
}
// update book
public function update($id, Request $request)
{
$book = Book::find($id);
$book->update($request->all());
return response()->json('The book successfully updated');
}
// delete book
public function delete($id)
{
$book = Book::find($id);
$book->delete();
return response()->json('The book successfully deleted');
}
}
Define Laravel Routes
Our model, migration and controller are ready to use. Let’s define the web and API routes. Open web.php from routes folder and register this route:
<?php
Route::get('{any}', function () {
return view('app');
})->where('any', '.*');
Open api.php and define CRUD routes like these:
<?php
use Illuminate\Http\Request;
Route::middleware('auth:api')->get('user', function (Request $request) {
return $request->user();
});
Route::get('books', 'BookController@index');
Route::group(['prefix' => 'book'], function () {
Route::post('add', 'BookController@add');
Route::get('edit/{id}', 'BookController@edit');
Route::post('update/{id}', 'BookController@update');
Route::delete('delete/{id}', 'BookController@delete');
});
Create Vue app
To declaratively render data to the DOM using Vue.js we need to declare Vue app. Navigate to resources>views folder and create a file called app.blade.php. Then paste this code:
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" value="{{ csrf_token() }}"/>
<title>Laravel & Vue CRUD Single Page Application (SPA) Tutorial - MyNotePaper</title>
<link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet" type="text/css">
<link href="{{ mix('css/app.css') }}" type="text/css" rel="stylesheet"/>
<style>
.bg-light {
background-color: #eae9e9 !important;
}
</style>
</head>
<body>
<div id="app">
</div>
<script src="{{ mix('js/app.js') }}" type="text/javascript"></script>
</body>
</html>
Create Vue Components
We are going to add 4 Vue components.
- App.vue
- AllBooks.vue
- AddBook.vue
- EditBook.vue
App.vue is the main Vue file. We will define router- view in the file. So all route pages will be shown in the App.vue file
Go to resources>js folder and create a file called App.vue and paste this code:
<template>
<div class="container">
<div class="text-center" style="margin: 20px 0px 20px 0px;">
<a href="https://shouts.dev/" target="_blank"><img src="https://i.imgur.com/hHZjfUq.png"></a><br>
<span class="text-secondary">Laravel & Vue CRUD Single Page Application (SPA) Tutorial</span>
</div>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="collapse navbar-collapse">
<div class="navbar-nav">
<router-link to="/" class="nav-item nav-link">Home</router-link>
<router-link to="/add" class="nav-item nav-link">Add Book</router-link>
</div>
</div>
</nav>
<br/>
<router-view></router-view>
</div>
</template>
<script>
export default {}
</script>
In the resources>js create a folder called components and go to the folder. We are going to create the rest 3 Vue components here. We So, let’s create.
<template>
<div>
<h3 class="text-center">All Books</h3><br/>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Author</th>
<th>Created At</th>
<th>Updated At</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="book in books" :key="book.id">
<td>{{ book.id }}</td>
<td>{{ book.name }}</td>
<td>{{ book.author }}</td>
<td>{{ book.created_at }}</td>
<td>{{ book.updated_at }}</td>
<td>
<div class="btn-group" role="group">
<router-link :to="{name: 'edit', params: { id: book.id }}" class="btn btn-primary">Edit
</router-link>
<button class="btn btn-danger" @click="deleteBook(book.id)">Delete</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
data() {
return {
books: []
}
},
created() {
this.axios
.get('http://localhost:8000/api/books')
.then(response => {
this.books = response.data;
});
},
methods: {
deleteBook(id) {
this.axios
.delete(`http://localhost:8000/api/book/delete/${id}`)
.then(response => {
let i = this.books.map(item => item.id).indexOf(id); // find index of your object
this.books.splice(i, 1)
});
}
}
}
</script>
<template>
<div>
<h3 class="text-center">Add Book</h3>
<div class="row">
<div class="col-md-6">
<form @submit.prevent="addBook">
<div class="form-group">
<label>Name</label>
<input type="text" class="form-control" v-model="book.name">
</div>
<div class="form-group">
<label>Author</label>
<input type="text" class="form-control" v-model="book.author">
</div>
<button type="submit" class="btn btn-primary">Add Book</button>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
book: {}
}
},
methods: {
addBook() {
this.axios
.post('http://localhost:8000/api/book/add', this.book)
.then(response => (
this.$router.push({name: 'home'})
// console.log(response.data)
))
.catch(error => console.log(error))
.finally(() => this.loading = false)
}
}
}
</script>
<template>
<div>
<h3 class="text-center">Edit Book</h3>
<div class="row">
<div class="col-md-6">
<form @submit.prevent="updateBook">
<div class="form-group">
<label>Name</label>
<input type="text" class="form-control" v-model="book.name">
</div>
<div class="form-group">
<label>Author</label>
<input type="text" class="form-control" v-model="book.author">
</div>
<button type="submit" class="btn btn-primary">Update Book</button>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
book: {}
}
},
created() {
this.axios
.get(`http://localhost:8000/api/book/edit/${this.$route.params.id}`)
.then((response) => {
this.book = response.data;
// console.log(response.data);
});
},
methods: {
updateBook() {
this.axios
.post(`http://localhost:8000/api/book/update/${this.$route.params.id}`, this.book)
.then((response) => {
this.$router.push({name: 'home'});
});
}
}
}
</script>
In the three files, we have used Axios to call Laravel API.
Define Vue Routes
In the resources>js folder, create a file named routes.js and paste this code:
import AllBooks from './components/AllBooks.vue';
import AddBook from './components/AddBook.vue';
import EditBook from './components/EditBook.vue';
export const routes = [
{
name: 'home',
path: '/',
component: AllBooks
},
{
name: 'add',
path: '/add',
component: AddBook
},
{
name: 'edit',
path: '/edit/:id',
component: EditBook
}
];
Import All to app.js
We are about to finish. This is the last step. Open/create app.js from resources>js folder and paste this code:
require('./bootstrap');
window.Vue = require('vue');
import App from './App.vue';
import VueRouter from 'vue-router';
import VueAxios from 'vue-axios';
import axios from 'axios';
import {routes} from './routes';
Vue.use(VueRouter);
Vue.use(VueAxios, axios);
const router = new VueRouter({
mode: 'history',
routes: routes
});
const app = new Vue({
el: '#app',
router: router,
render: h => h(App),
});
In this file, we have imported all necessary dependencies, routes etc.
The Output
We have completed all the tasks. Let’s run the project and see the output:
Issue: ‘axios’ of undefined
If you face any issue like “Uncaught TypeError: Cannot set property ‘axios’ of undefined”, then change the code of resources>js>app.js to:
require('./bootstrap');
window.Vue = require('vue');
import App from './App.vue';
import VueRouter from 'vue-router';
import axios from 'axios';
import {routes} from './routes';
Vue.use(VueRouter);
Vue.prototype.axios = axios;
const router = new VueRouter({
mode: 'history',
routes: routes
})
const app = new Vue({
el: '#app',
router: router,
render: h => h(App),
});
Thanks Cristian for providing the solution. ?
We are done. You can download this project from GitHub. Thanks for reading.
Md Obydullah
Software Engineer | Ethical Hacker & Cybersecurity...
Md Obydullah is a software engineer and full stack developer specialist at Laravel, Django, Vue.js, Node.js, Android, Linux Server, and Ethichal Hacking.