Laravel & Vue CRUD Single Page Application (SPA) Tutorial

Last modified on August 15, 2020 6 min read

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.

Table of Contents

  1. Install Laravel and NPM Dependencies
  2. Create Migration, Model and Controller
  3. Define Laravel Routes
  4. Create Vue App
  5. Create Vue Components
  6. Define Vue Routes
  7. Import All to app.js
  8. The Output

Step 1 : 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.

After installing Laravel, run this command to install frontend dependencies:

composer require laravel/ui
php artisan ui vue
npm install

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.

Step 2 : 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:

create_books_table.php
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:

Book.php
<?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:

BookController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Resources\BookCollection;
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');
    }
}

Step 3 : 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:

web.php
<?php

Route::get('{any}', function () {
    return view('app');
})->where('any', '.*');

Open api.php and define CRUD routes like these:

api.php
<?php

use Illuminate\Http\Request;

Route::middleware('auth:api')->get('user', function (Request $request) {
    return $request->user();
});

Route::get('books', '[email protected]');
Route::group(['prefix' => 'book'], function () {
    Route::post('add', '[email protected]');
    Route::get('edit/{id}', '[email protected]');
    Route::post('update/{id}', '[email protected]');
    Route::delete('delete/{id}', '[email protected]');
});

Step 4 : 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:

app.blade.php
<!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>

Step 5 : 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:

App.vue
<template>
    <div class="container">
        <div class="text-center" style="margin: 20px 0px 20px 0px;">
            <a href="https://www.mynotepaper.com/" 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.

AllBooks.vue
<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>
AddBook.vue
<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>
EditBook.vue
<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.

Step 6 : Define Vue Routes

In the resources>js folder, create a file named routes.js and paste this code:

routes.js
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
    }
];

Step 7 : 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:

app.js
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.

Step 8 : The Output

We have completed all the tasks. Let’s run the project and see the output:

We are done. You can download this project from GitHub. Thanks for reading. 馃檪

Author

Hey, I'm Md Obydullah. I build open-source projects and write on Laravel, Linux server, modern JavaScript and more on web development.

Follow

45 Replies to “Laravel & Vue CRUD Single Page Application (SPA) Tutorial”

  1. Thank you for this tutorial, it works like a charm!
    Your blog has been really useful to increase my knowledge in Laravel so keep it up!

  2. Hi,
    thank you very much !
    I’m just not able to read the data in my edit component. Here is my data object and my get method:
    data() {
    return {
    post: {}
    }
    },
    methods: {
    loadPost() {
    axios.get(‘api/posts/’+this.$route.params.id)
    .then((response) => { this.post = response.data })
    .catch()
    }
    }
    In the vue console i can see my post object with the data, but in my input the v-model does not show anything…

    Any help will be very appreciated !

        1. Thank you for reply !
          I can read the data as expected in my /post/:id/edit but only if i disable the history mode for vue-router… don’t know why…

          1. Ahh Okay. I’ll inform you by email if I find the solution.

            If you face any critical issues, you are welcome to inform me. I’ll try my best to provide a solution.

            Thank you, Julien. 馃檪

  3. I followed your instruction but when I click the add button, I am getting the error message in the console

    Failed to load resource: net::ERR_CONNECTION_TIMED_OUT

    Please help

    1. Hello Hussain,

      I’ve checked all the steps. It should work. Please check all the steps. Then run the app and test. You can also pull the project from GitHub and try to run the project. After that, if you still face this issue, please send me the project files as zip to my email.

      My email: [email protected]

      Thanks.

      1. Thank you, Obydullah. I think you have this problem, Hussain.

        [
        from origin ‘null’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check:
        ] (you can find the errors in console tab of inspect option of your browser)

        for fixing it, use this package:https://github.com/fruitcake/laravel-cors

  4. Thank you very much for your quick response. API Routes also I checked and I even clone your project from Github I will send you the project folder to you by email. Please check. Thank you very much for your kind support.
    Email sent (We Transfer)

  5. Hello, Thanks for sharing your code, I already follow all your instruction.
    I newly on laravel, so maybe I might be asking silly question :
    1. How I can change the code e.g. for header on App.vue (Since when I change the code thy not changing?)
    2. I don’t know why but when I try edit I got error like this (On Console):
    app.js:23717 [Vue warn]: Failed to mount component: template or render function not defined.
    found in
    —> at resources/js/components/EditBook.vue
    at resources/js/App.vue

    The code on app.js:23717 is console.error((“[Vue warn]: ” + msg + trace));

    Already try change component on routes.js from “component: EditBook” to “component: require(‘./components/EditBook.vue’).default”.
    But still not working. Any idea?

  6. I guess need run ‘npm run watch’ for update source code.

    Also I got error when intall npm (sorry late mention it).

    npm WARN using –force I sure hope you know what you are doing.
    C:\xampp\htdocs\laravel>npm install
    npm WARN deprecated [email protected]: Chokidar 2 will break on node v14+. Upgrade
    to chokidar 3 with 15x less dependencies.
    npm WARN deprecated [email protected]: fsevents 1 will break on node v14+ and coul
    d be using insecure binaries. Upgrade to fsevents 2.
    npm WARN deprecated [email protected]: Please see https://github.com/lydell/urix#deprec
    ated
    npm WARN deprecated [email protected]: https://github.com/lydell/resolve-url#dep
    recated
    npm notice created a lockfile as package-lock.json. You should commit this file.

    npm WARN optional SKIPPING OPTIONAL DEPENDENCY: [email protected]^1.2.7 (node_modules\ch
    okidar\node_modules\fsevents):
    npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for [email protected]
    1.2.13: wanted {“os”:”darwin”,”arch”:”any”} (current: {“os”:”win32″,”arch”:”x64″
    })
    npm WARN notsup Unsupported engine for [email protected]: wanted: {“node
    “:”= 3.1.0 but none is instal
    led. You must install peer dependencies yourself.

    added 1119 packages from 496 contributors and audited 1122 packages in 192.466s
    found 1 low severity vulnerability
    run `npm audit fix` to fix them, or `npm audit` for details

    Already try
    1. ‘npm audit fix’ since everything is fine so I guess that okay, but I got error whn run ‘npm run watch’

    npm ERR! A complete log of this run can be found in:
    npm ERR! C:\Users\****\AppData\Roaming\npm-cache\_logs\2020-06-02T11_07_53_7
    45Z-debug.log
    npm ERR! code ELIFECYCLE
    npm ERR! errno 1
    npm ERR! @ watch: `npm run development — –watch`
    npm ERR! Exit status 1
    npm ERR!
    npm ERR! Failed at the @ watch script.
    npm ERR! This is probably not a problem with npm. There is likely additional log
    ging output above.

    npm ERR! A complete log of this run can be found in:
    npm ERR! C:\Users\****\AppData\Roaming\npm-cache\_logs\2020-06-02T11_07_53_7
    97Z-debug.log

    So I try install again
    2. Clear the npm and install again
    – npm cache clean –force
    – rm -rf node_modules
    – npm install
    – npm run dev
    – npm run watch
    3. Clear the npm and install on global (like 2 not npm install -g).
    4. Clear the npm and install lower version npm install -g [email protected].1
    Still no luck, any idea?

    1. Hi Zero,

      You’re welcome to Laravel + Vue. Yh, you need to run npm run watch to update output instantly.

      I’ve read your all messages. To solve the issues, you can completely remove Node.js from your PC and install the latest version of Node.js.

      After that you can clone the project from GitHub and try to run without changing anything. If it works, then try to change the code.

      Still, if you fail to run, please email me at [email protected].

      Best wishes. Thanks. 馃檪

  7. En mi caso funciona sin problemas pero al ejecutarlo no tiene el formato que deberia tener, esta todo feo, pero funciona, quisiera saber a que se debe que este error, si es algo que no tengo

  8. Copied and pasted. Getting nothing but server 500 errors. Doesn’t work with homestead. Even after removing the localhost prefix.

    1. Hi Casey,

      First, check database credentials in .env file. If everything is okay and shows 500 error then run these commands and check:
      php artisan key:generate
      php artisan cache:clear
      php artisan config:clear
      composer dump-autoload

      Thanks.

  9. Hi, with the latest Laravel, v. 7.18
    for perfect installation,
    before the command “npm install”
    the following commands must be started
    > composer require laravel/ui
    > php artisan ui vue
    G.

  10. Bro why you not show URL ? My project is work but not display any data or insert data so how can i fix it.!

  11. Hello, brother. Your tutorial is so awesome. but i get in trouble. when i click delete button, it’s not deleted. can you help me? thanks. i’m sorry for my bad english

    1. Hi Rahmad Subagiyo,

      Thanks for your compliments, bro. 馃檪 Can you give a screenshot of the console log error? Click the delete button and check the console log error. You can send me the screenshot via email. My email: [email protected]

      Thank you.

  12. Thank you very much for sharing.. the index and adding data work perfectly, but for deleting and so on i get trouble, it doesnt work.. btw my axios path for ex. delete .delete(‘http://localhost/Laravel/api/book/delete/${id}’) it should go to api route for deleting function.. is the path getting wrong so the function not work ?

Leave a Reply

Your email address will not be published. Required fields are marked *