Backend DevelopmentFrontend DevelopmentVueJs Tutorials

Laravel 8 CRUD Using Inertiajs and Vuejs 3

Laravel 8 CRUD Using Inertiajs and Vuejs 3

In this tutorial we will learn how to use inertiajs in laravel 8 and Vue 3 to build CRUD (CREATE-READ-UPDATE-DELETE) pages.

 

 

 

This tutorial requires that you know how to use laravel and Vuejs. We will use laravel 8 and Vuejs 3 and also we will use inertiajs, if you don’t know inertiajs before so no problem, i will give a brief description about it below or you can learn more about it by clicking this url. I will use bootstrap as the UI library.

 

Overview About Interiajs

Interiajs is a library designed to be a middle layer between the frontend application and the backend. For example if you plan to build an application using laravel and vuejs, the first thing you take in consideration is Building an Api to connect the frontend with the backend.

But using interia doesn’t require an api, instead you build the controllers in the normal way which accept a request and return an interia response. The second thing is that interiajs doesn’t require that you create routes in vuejs instead we will use the normal laravel routes and it will work the same way as the vue routes. Also intertiajs provides other features like redirections, form submissions, shared data, and more.

 

Preparing Project

Let’s get started, at first make sure you have PHP 8. Next in your server root create a new laravel project using composer:

composer create-project laravel/laravel laravel-inertia-crud

Then after successful creation cd into the project:

cd laravel-inertia-crud

 

Installing Dependencies

Inertiajs has two packages one server side and the other client side so let’s install the server side package first:

composer require inertiajs/inertia-laravel

Also install this useful package “tightenco/ziggy”, this package enable us to access laravel routes from javascript so we will use it in vue components like so route(‘route name’):

composer require tightenco/ziggy

Then prepare the root template in the main layout. So create this file app.blade.php in resources/views. This file will act as the main template for the inertia app.

resources/views/app.blade.php

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Laravel Inertia CRUD</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />

    <link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css" />

    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" />

    <link href="{{ asset('/css/app.css') }}" rel="stylesheet" />

    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>

    @routes
    <script src="{{ asset('/js/app.js') }}" defer></script>
</head>
<body>
    <div class="container">
        @inertia
    </div>
</body>
</html>

In this code i added the @inertia directive. This directive will be replaced at runtime by the root div that will be mounted by the inertia app. Note also the @routes directive which provided by the tightenc/ziggy package.

Next setup the inertia middleware using this command:

php artisan inertia:middleware

Register the middleware in app/Http/Kernel.php as the last item in the web middleware group:

'web' => [
    // ...
    \App\Http\Middleware\HandleInertiaRequests::class,
],

After installing the server side package, it’s time to install the client side package with npm.

Run this command to install the npm packages first:

npm install

Then install the inertia npm package using:

npm install @inertiajs/inertia @inertiajs/inertia-vue3

Here we will use inertia with vue 3 so we also need to install vue 3 and vue-loader:

npm install vue@next vue-loader@^16.2.0 @vue/compiler-sfc@^3.0.11

You can also install the inertia progress package which a display progress bar when navigating between pages:

npm install @inertiajs/progress

 

Database & Migration

Let’s create a new mysql database using phpmyadmin name it as you like then update laravel db settings in the .env file as follows:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_inertia_crud
DB_USERNAME=<DB username>
DB_PASSWORD=<DB password>

Next create a new migration, we will create a posts table with this command:

php artisan make:migration create_posts_table

Then open the migration you just create and update the up() method like so:

public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string("title");
            $table->text("content");
            $table->string("image")->nullable();
            $table->timestamps();
        });
    }

Here we are creating a posts table with title, content, and image fields. Finally migrate the tables using:

php artisan migrate

This will create the posts table along side other tables like the users table which we will use to authenticate users.

 

Project Structure

Now let’s talk about the project structure, we will need these pages:

  • Login Form
  • Register Form
  • Page to view all posts
  • Create Post Page
  • Edit Post Page.

Inertiajs suggests that every controller action has a Vue component, so based on this convention let’s create the controllers first in app/Http/Controllers, we will create the controllers structure first then we will update them later.

For the authentication, create a new folder “Auth” in app/Http/Controllers, then create two files inside it, LoginController.php and RegisterController.php.

app/Http/Controllers/Auth/LoginController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;

class LoginController extends Controller
{
    public function showLoginForm()
    {
        return Inertia::render('Auth/Login');   // Vue component
    }

    public function authenticate(Request $request)
    {
        
    }

    public function logout(Request $request)
    {
        
    }
}

app/Http/Controllers/Auth/RegisterController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Redirect;
use Inertia\Inertia;

class RegisterController extends Controller
{
    public function showRegisterForm()
    {
        return Inertia::render('Auth/Register');
    }

    public function register(Request $request)
    {
        
    }
}

Create a resource controller for the posts:

php artisan make:controller PostsController --resource

Open the PostsController and update like so:

PostsController.php

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Inertia\Inertia;

class PostsController extends Controller
{
    public function __construct()
    {
        $this->middleware("auth")->except(["index"]);
    }

    public function index()
    {
        return Inertia::render('Posts/Index', [
            "posts" => Post::orderBy('id', 'DESC')->paginate(10)
        ]);
    }

    public function create()
    {
        return Inertia::render('Posts/Create');
    }

    public function store(Request $request)
    {
        
    }

    public function edit($id)
    {
        return Inertia::render('Posts/Edit', [
            'post' => Post::findOrFail($id)
        ]);
    }

    public function update(Request $request, $id)
    {
        
    }

    public function destroy(Request $request, $id)
    {
        
    }
}

As you see in the above controllers i invoked Inertia::render() method in the Inertia facade, this method act like the view() method which works with blade views, instead this method accept a Vue component name and an optional parameter that specify the props we want to send to the component, for example in the PostsController::edit($id) the method render “Posts/Edit” component and we send the post object as a prop.

 

Creating The Routes

As we mentioned previously that we don’t need vue routes instead we will create the routes as usual in laravel, so open routes/web.php and add those routes:

<?php

use App\Http\Controllers\Auth\LoginController;
use App\Http\Controllers\Auth\RegisterController;
use App\Http\Controllers\PostsController;
use Illuminate\Support\Facades\Route;


Route::get('login', [LoginController::class, 'showLoginForm'])->name('showLoginForm')->middleware('guest');
Route::get('register', [RegisterController::class, 'showRegisterForm'])->name('showRegisterForm')->middleware('guest');

Route::post('login', [LoginController::class, 'authenticate'])->name('login');
Route::post('register', [RegisterController::class, 'register'])->name('register');

Route::post('logout', [LoginController::class, 'logout'])->name('logout');

Route::resource('post', PostsController::class);

Route::redirect('/', 'post');

Now after we created the controllers and routes let’s create the Vue components and pages, this will be in resources/js/ directory. Open resources/js directory create these directories:

  • Pages/Auth/
  • Pages/Posts/

Then create these components inside Pages/Auth/ directory:

  • Login.vue
  • Register.vue

In the same way create these components inside Pages/Posts/ directory:

  • Create.vue
  • Edit.vue
  • Index.vue

Update app.js

Open resources/js/app.js and update it like so:

require('./bootstrap');

import {createApp, h} from 'vue';
import { App, plugin } from '@inertiajs/inertia-vue3';
import { InertiaProgress } from '@inertiajs/progress'

const el = document.getElementById('app');

InertiaProgress.init();


    const app = createApp({
        render: () => h(App, {
            initialPage: JSON.parse(el.dataset.page),
            resolveComponent: name => require(`./Pages/${name}`).default
        })
    });

    app.config.globalProperties.$route = window.route;
    app.provide('$route', window.route);

    app.use(plugin).mount(el);

This file simply start the inertia app mounting process, here we are telling inertia to mount the app in an element whose id is “app”, then we use the Vue 3 way of creating app using the createApp() function, this function available in Vue 3 only.

The createApp() function takes an object, which contains the render() function, the render function as follows:

render: () => h(App, {
            initialPage: JSON.parse(el.dataset.page),
            resolveComponent: name => require(`./Pages/${name}`).default
        })

So what are the intialPage and resolveComponent? the initialPage parameter represent the data sent from laravel to the Vue app as a property in the root element like so:

<div id="app" data-page="{}" data-v-app="">

   <!-- Component Body -->

</div>

As you see in each page you navigate the above attribute data-page will be populated with the required data to render the component.

The resolveComponent parameter is a callback that specifies where to load the components, in this case we are rendering the components located in resources/js/Pages/ directory.

These two lines app.config.globalProperties.$route and app.provide(‘$route’, window.route) tell Vue to inject the route() function, this function available from the ziggy package we installed above so can access it in any component like this.$route().

Open resources/css/app.css and add this css code:

.alert li {
    font-size: 14px;
}

.logout-link {
    border: none;
    background: none;
    display: inline;
    color: #fff !important;
}

Update webpack.mix.js

const mix = require('laravel-mix');

mix.js('resources/js/app.js', 'public/js').vue()
    .postCss('resources/css/app.css', 'public/css', [
    ]);

This is important so that vue 3 can build successfully.

 

Update HandleInertiaRequests Middleware

An important thing that must be done is to update the HandleInertiaRequests::share() method. The share() method allow us to share global data that is accessible in every Vue page instead of sending this data for controller action. So we will share the current authenticated user and the flash message and the csrf_token.

app/Http/Middleware/HandleInertiaRequests.php

<?php

namespace App\Http\Middleware;

use Illuminate\Http\Request;
use Inertia\Middleware;

class HandleInertiaRequests extends Middleware
{
    
    protected $rootView = 'app';

    /**
     * Determines the current asset version.
     *
     */
    public function version(Request $request)
    {
        return parent::version($request);
    }

    
    public function share(Request $request)
    {
        return array_merge(parent::share($request), [
            'csrf_token' => csrf_token(),
            'flash' => [
                'success' => fn () => $request->session()->get('success')
            ],
            'auth.user' => fn () => $request->user() ? $request->user()->only('id', 'name', 'email') : null
        ]);
    }
}

 

At this point let’s go a head and focus on updating each component separately.

Registration

Open resources/js/Pages/Auth/Register.vue and update like so:

<template>
        <app-header></app-header>
        <div class="row">
            <div class="col-md-6 offset-md-3">
                <form method="post" @submit.prevent="submit">
                    <h2 class="text-center">Create New Account</h2>

                    <errors-and-messages :errors="errors"></errors-and-messages>

                    <div class="form-group">
                        <label for="name">Name</label>
                        <input type="text" class="form-control" name="name" id="name" v-model="form.name" />
                    </div>

                    <div class="form-group">
                        <label for="email">Email</label>
                        <input type="text" class="form-control" name="email" id="email" v-model="form.email" />
                    </div>

                    <div class="form-group">
                        <label for="password">Password</label>
                        <input type="password" class="form-control" name="password" id="password" v-model="form.password" />
                    </div>

                    <input type="submit" class="btn btn-primary btn-block" value="Register" />
                </form>
            </div>
        </div>

</template>

<script>
import AppHeader from "../../Partials/AppHeader";
import ErrorsAndMessages from "../../Partials/ErrorsAndMessages";

import {inject, reactive} from "vue";
import {usePage} from "@inertiajs/inertia-vue3";
import {Inertia} from "@inertiajs/inertia";

export default {
    name: "Register",
    components: {
        ErrorsAndMessages,
        AppHeader
    },
    props: {
        errors: Object
    },
    setup() {
        const form = reactive({
            name: null,
            email: null,
            password: null,
            _token: usePage().props.value.csrf_token
        });

        const route = inject('$route');

        function submit() {
            Inertia.post(route('register'), form);
        }

        return {
            form, submit
        }
    }
}
</script>

<style scoped>
    form {
        margin-top: 20px;
    }
</style>

This is the registration form, as you see we are including other partial components which are the <app-header /> and <errors-and-messages /> we will create them below. Next i used Vue 3 composition api through the use of the setup() function, to learn more about Vue 3 composition Api click this url.

Inside the setup() function we do a lot of stuff. First we declared the form object which contains the name, email and password and the _token as a reactive object. When declaring an object inside reactive then it will perform well on two way model binding.

To retrieve any prop from the backend you can use usePage().props.value.propname as we done for the csrf_token prop above. 

Then i declared the submit() function that will submit the form to the server. The function makes a post request using Inertia.post(url, params) function. There are also other functions like Inertia.get(), Inertia.put() etc. Note also that this is different from making ajax requests because this request treated as a normal server side request. The url here is the register route obtained from route(‘register’) function provided to us by the ziggy package. The setup() function then return the form object and the submit() function.

Finally in the form i binded the form fields using v-model passing the form object and accessing each field property separately, then i submit the form by listening of @submit event on the form element.

Now after completing the vue component let’s update the server side code, this will be in app/Http/Controllers/Auth/RegisterController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Redirect;
use Inertia\Inertia;

class RegisterController extends Controller
{
    public function showRegisterForm()
    {
        return Inertia::render('Auth/Register');
    }

    public function register(Request $request)
    {
        $this->validate($request, [
            'name' => ['required', 'max:100'],
            'email' => ['required', 'email', 'unique:users'],
            'password' => ['required', 'min:4']
        ]);

        $user = new User();
        $user->name = $request->input("name");
        $user->email = $request->input("email");
        $user->password = Hash::make($request->input("password"));
        $user->save();

        $request->session()->flash('success', 'User registered successfully! you can sign in now');

        return Redirect::route('showLoginForm');
    }
}

In the register() method i validate the incoming request, then saving the user, after that i trigger a success flash message using laravel $request->session()->flash(), finally i redirect the user to the login page. As notable here we make the redirection and the flash message using the normal laravel way and inertiajs will translate this under the hood.

 

Partial Components

resources/js/Partials/AppHeader.vue

<template>
    <header>
        <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
            <inertia-link :href="$route('post.index')" class="navbar-brand">Inertia CRUD</inertia-link>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>

            <div class="collapse navbar-collapse" id="navbarSupportedContent">
                <ul class="navbar-nav mr-auto">
                    <li class="nav-item active">
                        <inertia-link :href="$route('post.index')" class="nav-link">Home</inertia-link>
                    </li>
                    <li class="nav-item">
                        <inertia-link :href="$route('post.create')" class="nav-link">Create Post</inertia-link>
                    </li>
                </ul>
            </div>

            <div class="navbar-collapse collapse order-3 dual-collapse2">
                <ul class="navbar-nav ml-auto">
                    <li class="nav-item" v-if="!user">
                        <inertia-link :href="$route('showLoginForm')" class="nav-link">Login</inertia-link>
                    </li>
                    <li class="nav-item" v-if="!user">
                        <inertia-link :href="$route('showRegisterForm')" class="nav-link">Register</inertia-link>
                    </li>
                    <li class="nav-item" v-if="user">
                        <span class="navbar-text" v-if="user">
                            Logged in as {{user.name}}
                        </span>
                        <inertia-link :href="$route('logout')" as="button" method="post" class="nav-link logout-link" style="display: inline" type="button">Logout</inertia-link>
                    </li>
                </ul>
            </div>
        </nav>
    </header>
</template>

<script>
import {computed} from "vue";
import {usePage} from "@inertiajs/inertia-vue3";

export default {
    name: "AppHeader",
    setup() {
        const user = computed(() => usePage().props.value.auth.user);

        return {
            user
        }
    }
}
</script>

resources/js/Partials/ErrorsAndMessages.vue

<template>
    <ul class="alert alert-danger" v-if="errors && Object.keys(errors).length > 0">
        <li v-for="error in errors">{{error}}</li>
    </ul>
    <div class="alert alert-success" v-if="$page.props.flash.success">
        {{$page.props.flash.success}}
    </div>
</template>

<script>
export default {
    name: "ErrorsAndMessages",
    props: ["errors"]
}
</script>

 

In the same way let’s continue completing the other pages.

Login

Open and update resources/js/Pages/Auth/Login.vue

<template>
        <app-header></app-header>
        <div class="row">
            <div class="col-md-6 offset-md-3">
                <form method="post" @submit.prevent="submit">
                    <h2 class="text-center">Sign In</h2>

                    <errors-and-messages :errors="errors"></errors-and-messages>

                    <div class="form-group">
                        <label for="email">Email</label>
                        <input type="text" class="form-control" name="email" id="email" v-model="form.email" />
                    </div>

                    <div class="form-group">
                        <label for="password">Password</label>
                        <input type="password" class="form-control" name="password" id="password" v-model="form.password" />
                    </div>

                    <input type="submit" class="btn btn-primary btn-block" value="Login" />
                </form>
            </div>
        </div>

</template>

<script>
    import AppHeader from "../../Partials/AppHeader";
    import ErrorsAndMessages from "../../Partials/ErrorsAndMessages";

    import {Inertia} from "@inertiajs/inertia";
    import { usePage } from '@inertiajs/inertia-vue3'
    import {reactive,inject} from 'vue';

    export default {
        components: {
            ErrorsAndMessages,
            AppHeader
        },
        name: "Login",
        props: {
            errors: Object
        },
        setup() {
            const form = reactive({
               email: null,
               password: null,
               _token: usePage().props.value.csrf_token
            });

            const route = inject('$route');

            function submit() {
                Inertia.post(route('login'), form);
            }

            return {
                form, submit
            }
        }
    }
</script>

<style scoped>
    form {
        margin-top: 20px;
    }
</style>

As you see above i am doing the same thing as the register, now we need to update the login controller.

app/Http/Controllers/Auth/LoginController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;

class LoginController extends Controller
{
    public function showLoginForm()
    {
        return Inertia::render('Auth/Login');
    }

    public function authenticate(Request $request)
    {
        $credentials = $this->validate($request, [
            'email' => ['required', 'email'],
            'password' => ['required']
        ]);

        if(Auth::attempt($credentials)) {
            $request->session()->regenerate();

            return redirect()->intended('/');
        }

        return back()->withErrors([
            'email' => 'The provided credentials do not match our records.',
        ]);
    }

    public function logout(Request $request)
    {
        Auth::logout();

        $request->session()->invalidate();

        $request->session()->regenerateToken();

        return redirect('/');
    }
}

In some situations the inertia-link is not recognized and show this error “inertia-link” can not be found. Resolve this by adding this line in app.js

app.component(‘inertia-link’, InertiaLink);

Posts Manipulation

  • Create Post Component

Update resources/js/Pages/Posts/Create.vue

<template>
    <app-header></app-header>

    <div class="row">
        <div class="col-md-6 offset-md-3">
            <form method="post" @submit.prevent="submit">
                <h2 class="text-left">Create Post</h2>

                <errors-and-messages :errors="errors"></errors-and-messages>

                <div class="form-group">
                    <label for="title">Title</label>
                    <input type="text" class="form-control" name="title" id="title" v-model="form.title" />
                </div>

                <div class="form-group">
                    <label for="content">Content</label>
                    <textarea id="content" name="content" class="form-control" v-model="form.content"></textarea>
                </div>

                <div class="form-group">
                    <label for="image">Image</label>
                    <input type="file" id="image" name="image" class="form-control" @change="selectFile">
                </div>

                <input type="submit" class="btn btn-primary btn-block" value="Save" />
            </form>
        </div>
    </div>
</template>

<script>
import AppHeader from "../../Partials/AppHeader";
import ErrorsAndMessages from "../../Partials/ErrorsAndMessages";
import {inject, reactive} from "vue";
import {Inertia} from "@inertiajs/inertia";
import {usePage} from "@inertiajs/inertia-vue3";

export default {
    name: "Create",
    components: {
        ErrorsAndMessages,
        AppHeader
    },
    props: {
        errors: Object
    },
    setup() {
        const form = reactive({
            title: null,
            content: null,
            image: null,
            _token: usePage().props.value.csrf_token
        });

        const route = inject('$route');

        function selectFile($event) {
            form.image = $event.target.files[0];
        }

        function submit() {
            Inertia.post(route('post.store'), form, {
                forceFormData: true
            });
        }

        return {
            form, submit, selectFile
        }
    }
}
</script>

<style scoped>
    form {
        margin-top: 20px;
    }
</style>
  • List Posts Component

Update resources/js/Pages/Posts/Index.vue

<template>
        <app-header></app-header>

        <div class="row">
            <div class="col-md-12" style="margin-top: 20px">
                <h2 class="text-left">All Posts</h2>

                <errors-and-messages :errors="errors"></errors-and-messages>

                <div v-if="posts.data.length > 0">

                    <div class="col-md-10 article" v-for="post in posts.data" :key="post.id">
                        <h4>{{post.title}}</h4>
                        <img v-if="post.image_url" width="300" height="250" :src="post.image_url" class="pull-left img-responsive thumb margin10 img-thumbnail">
                        <article>
                            <p>
                                {{ post.content }}
                            </p>
                        </article>
                        <inertia-link :href="$route('post.edit', {id: post.id})" class="btn btn-primary pull-right action-btn">Edit Post</inertia-link>
                        <a href="javascript:void(0);" class="btn btn-warning pull-right action-btn" @click.prevent="deletePost(post.id)"><i class="fas fa-trash-alt"></i> Delete Post</a>
                    </div>

                    <!-- Pagination links-->
                    <nav aria-label="Page navigation" v-if="posts.total > posts.per_page" style="margin-top: 20px">
                        <ul class="pagination">
                            <!-- Previous link -->
                            <li :class="'page-item' + (posts.links[0].url == null ? ' disabled' : '')">
                                <inertia-link :href="posts.links[0].url == null ? '#' : posts.links[0].url" class="page-link" v-html="posts.links[0].label"></inertia-link>
                            </li>

                            <!-- Numbers -->
                            <li v-for="item in numberLinks" :class="'page-item' + (item.active ? ' disabled' : '')">
                                <inertia-link :href="item.active ? '#' : item.url" class="page-link" v-html="item.label"></inertia-link>
                            </li>

                            <!-- Next link -->
                            <li :class="'page-item' + (posts.links[posts.links.length - 1].url == null ? ' disabled' : '')">
                                <inertia-link :href="posts.links[posts.links.length - 1].url == null ? '#' : posts.links[posts.links.length - 1].url" class="page-link" v-html="posts.links[posts.links.length - 1].label"></inertia-link>
                            </li>
                        </ul>
                    </nav>
                </div>
                <div class="text-center" v-else>
                    No posts found! <inertia-link :href="$route('post.create')">Create Post</inertia-link>
                </div>
            </div>
        </div>

</template>

<script>
import AppHeader from "../../Partials/AppHeader";
import ErrorsAndMessages from "../../Partials/ErrorsAndMessages";
import {usePage} from "@inertiajs/inertia-vue3";
import {Inertia} from "@inertiajs/inertia";
import {computed, inject} from "vue";

export default {
    name: "Posts",
    components: {
        ErrorsAndMessages,
        AppHeader
    },
    props: {
        errors: Object
    },
    setup() {
        const route = inject('$route');

        const deletePost = (id) => {
            if (!confirm('Are you sure?')) return;
            Inertia.delete(route('post.destroy', {id}));
        }

        const posts = computed(() => usePage().props.value.posts);

        const numberLinks = posts.value.links.filter((v, i) => i > 0 && i < posts.value.links.length - 1);

        return {
            posts,
            deletePost,
            numberLinks
        }
    }
}
</script>

<style scoped>
    .action-btn {
        margin-left: 12px;
        font-size: 13px;
    }

    .article {
        margin-top: 20px;
    }

</style>
  • Edit Post Component

Update resources/js/Pages/Posts/Edit.vue

<template>
    <app-header></app-header>

    <div class="row">
        <div class="col-md-6 offset-md-3">
            <form method="post" @submit.prevent="submit">
                <h2 class="text-left">Update Post</h2>

                <errors-and-messages :errors="errors"></errors-and-messages>

                <div class="form-group">
                    <label for="title">Title</label>
                    <input type="text" class="form-control" name="title" id="title" v-model="form.title" />
                </div>

                <div class="form-group">
                    <label for="content">Content</label>
                    <textarea id="content" name="content" class="form-control" v-model="form.content"></textarea>
                </div>

                <div class="form-group">
                    <label for="image">Image</label>
                    <img :src="image_url" width="100" height="90" v-if="image_url" />
                    <input type="file" id="image" name="image" class="form-control" @change="selectFile">
                </div>

                <input type="submit" class="btn btn-primary btn-block" value="Update" />
            </form>
        </div>
    </div>
</template>

<script>
import AppHeader from "../../Partials/AppHeader";
import {inject, reactive} from "vue";
import {Inertia} from "@inertiajs/inertia";
import ErrorsAndMessages from "../../Partials/ErrorsAndMessages";
import {usePage} from "@inertiajs/inertia-vue3";

export default {
    name: "Edit",
    components: {
        ErrorsAndMessages,
        AppHeader
    },
    props: {
        errors: Object
    },
    setup() {
        const form = reactive({
            title: null,
            content: null,
            image: null,
            _token: usePage().props.value.csrf_token,
            _method: "PUT"
        });

        // retrieve post prop
        const {title, content, image_url, id } = usePage().props.value.post;
        form.title = title;
        form.content = content;

        const route = inject('$route');

        function selectFile($event) {
            form.image = $event.target.files[0];
        }

        function submit() {
            Inertia.post(route('post.update', {'id': id}), form, {
                forceFormData: true
            });
        }

        return {
            form, submit, selectFile, image_url
        }
    }
}
</script>

<style scoped>
    form {
        margin-top: 20px;
    }
</style>
  • Update The Posts Controller

app/Http/Controllers/PostsController.php

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Inertia\Inertia;

class PostsController extends Controller
{
    public function __construct()
    {
        $this->middleware("auth")->except(["index"]);
    }

    public function index()
    {
        return Inertia::render('Posts/Index', [
            "posts" => Post::orderBy('id', 'DESC')->paginate(10)
        ]);
    }

    public function create()
    {
        return Inertia::render('Posts/Create');
    }

    public function store(Request $request)
    {
        $this->getValidate($request);

        $post = new Post();

        $post->title = $request->input('title');
        $post->content = $request->input('content');

        if($request->file('image')) {
            $post->image = $this->upload($request);
        }

        $post->save();

        $request->session()->flash('success', 'Post created successfully!');

        return redirect()->route('post.index');
    }

    public function edit($id)
    {
        return Inertia::render('Posts/Edit', [
            'post' => Post::findOrFail($id)
        ]);
    }

    public function update(Request $request, $id)
    {
        $this->getValidate($request, $id);

        $post = Post::find($id);

        $post->title = $request->input('title');
        $post->content = $request->input('content');

        if($request->file('image')) {
            $post->image = $this->upload($request);
        }

        $post->save();

        $request->session()->flash('success', 'Post updated successfully!');

        return redirect()->route('post.index');
    }

    public function destroy(Request $request, $id)
    {
        Post::find($id)->delete();

        $request->session()->flash('success', 'Post deleted successfully!');

        return redirect()->route('post.index');
    }

    /**
     * @param Request $request
     * @throws \Illuminate\Validation\ValidationException
     */
    private function getValidate(Request $request, $id = null): void
    {
        $data = [
            'title' => 'required',
            'content' => 'required'
        ];

        $this->validate($request, $data);
    }

    private function upload($request)
    {
        $image = $request->file('image');

        $imageName = md5(uniqid()) . "." . $image->getClientOriginalExtension();

        $image->move(public_path('uploads'), $imageName);

        return $imageName;
    }
}

Don’t forget to create a writable uploads/ directory inside public/ directory for storing uploaded images.

  • Update The Post Model

app/Models/Post.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;

    protected $appends = ['image_url'];

    function getImageUrlAttribute()
    {
        return $this->image ? url('/uploads/' . $this->image) : "";
    }
}

Now launch the application but first run this command to build the vue components:

npm run dev

Then navigate to http://localhost/laravel-inertia-crud/public/

Download Respository

 

3.5 6 votes
Article Rating

What's your reaction?

Excited
4
Happy
1
Not Sure
1
Confused
3

You may also like

Subscribe
Notify of
guest
7 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Gio
Gio
1 month ago

i recommend an update to this article. To resolve the “inertia-link” cannot be found.

add the following line in app.js

app.component(‘inertia-link’,InertiaLink);

erastus
erastus
24 days ago
Reply to  Gio

on which line of app.js ?

erastus
erastus
24 days ago
Reply to  Gio

I added

app.use(plugin).mount(el);

app.component(‘inertia-link’,InertiaLink);

getting below error

Uncaught ReferenceError: InertiaLink is not defined

Kanishka Sandaruwan Bandara
Kanishka Sandaruwan Bandara
22 days ago
Reply to  WebMobTuts

Is the Vue3 working well with inertia.js and laravel 8?Please can you guarantee.I am trying to start a project with these.