Backend Development

Handling Forms And Validations With Laravel Inertiajs

In this post we will learn how to submit forms and work with validations when working with Inertia-js library and Laraval.

 

 

If you have not used Inertia-js before, inertia is a library for building monolithic apps without creating an Api thereby reducing efforts regrading Api creation. When used beside laravel it acts as a bridge between the frontend App which can be (React or Vue) and the Server side App (Laravel).

In this post examples we are using Vue 3 as a frontend with Laravel. 

For the sake of simplicity i suppose we already have a laravel app with inertia installed, and we have simple PostsController with this code:

app/Http/PostsController.php

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Inertia\Inertia;
class PostsController extends Controller
{
public function create()
{
return Inertia::render('create-post');
}
public function store(Request $request)
{
//
}
}
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Inertia\Inertia; class PostsController extends Controller { public function create() { return Inertia::render('create-post'); } public function store(Request $request) { // } }
<?php

namespace App\Http\Controllers;

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

class PostsController extends Controller
{
    public function create()
    {
        return Inertia::render('create-post');
    }

    public function store(Request $request) 
    {
         // 
    }
}

The create() method renders the Vue page “create-post.vue”. let’s see the code for create-post.vue

resources/js/Pages/create-post.vue

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<script setup>
import {reactive} from 'vue';
const form = reactive({
title: '',
description: '',
author: ''
})
function save() {
console.log(form);
}
</script>
<template>
<form method="post" @submit.prevent="save" class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
<div class="mb-4">
<label class="block text-gray-700 text-sm font-bold mb-2">Title</label>
<input type="text" name="title" v-model="form.title" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700" />
</div>
<div class="mb-4">
<label class="block text-gray-700 text-sm font-bold mb-2">Description</label>
<textarea name="description" v-model="form.description" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700"></textarea>
</div>
<div class="mb-4">
<label class="block text-gray-700 text-sm font-bold mb-2">Author</label>
<input type="text" name="author" v-model="form.author" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700" />
</div>
<div>
<button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">Save</button>
</div>
</form>
</template>
<script setup> import {reactive} from 'vue'; const form = reactive({ title: '', description: '', author: '' }) function save() { console.log(form); } </script> <template> <form method="post" @submit.prevent="save" class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"> <div class="mb-4"> <label class="block text-gray-700 text-sm font-bold mb-2">Title</label> <input type="text" name="title" v-model="form.title" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700" /> </div> <div class="mb-4"> <label class="block text-gray-700 text-sm font-bold mb-2">Description</label> <textarea name="description" v-model="form.description" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700"></textarea> </div> <div class="mb-4"> <label class="block text-gray-700 text-sm font-bold mb-2">Author</label> <input type="text" name="author" v-model="form.author" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700" /> </div> <div> <button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">Save</button> </div> </form> </template>
<script setup>
    import {reactive} from 'vue';

    const form = reactive({
        title: '',
        description: '',
        author: ''
    })

    function save() {
        console.log(form);
    }
</script>

<template>
    <form method="post" @submit.prevent="save" class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
        <div class="mb-4">
            <label class="block text-gray-700 text-sm font-bold mb-2">Title</label>
            <input type="text" name="title" v-model="form.title" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700" />
        </div>
        <div class="mb-4">
            <label class="block text-gray-700 text-sm font-bold mb-2">Description</label>
            <textarea name="description" v-model="form.description" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700"></textarea>
        </div>
        <div class="mb-4">
            <label class="block text-gray-700 text-sm font-bold mb-2">Author</label>
            <input type="text" name="author" v-model="form.author" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700" />
        </div>
        <div>
            <button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">Save</button>
        </div>
    </form>
</template>

In this component i am using Vue 3 composition Api by utilizing the <script setup>. In the template i created a simple form to add a post styled using tailwindcss classes.

In the script the form is bound to a reactive object using vue 3 reactive function so that we can use it with v-model directive.

routes/web.php

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Route::get('posts/create', [\App\Http\Controllers\PostsController::class, 'create']);
Route::post('posts', [\App\Http\Controllers\PostsController::class, 'store']);
Route::get('posts/create', [\App\Http\Controllers\PostsController::class, 'create']); Route::post('posts', [\App\Http\Controllers\PostsController::class, 'store']);
Route::get('posts/create', [\App\Http\Controllers\PostsController::class, 'create']);
Route::post('posts', [\App\Http\Controllers\PostsController::class, 'store']);

Now when you navigate to the url http://<host>/posts/create , you will see the form.

Create a migration for posts table:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php artisan make:Model Post -m
php artisan make:Model Post -m
php artisan make:Model Post -m

This command creates a Post model and -m flag creates the associated migration.

The posts migration file:

Open database/migrations/xxxx_xx_xx_create_posts_table.php

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('description');
$table->string('author');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('posts');
}
};
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { /** * Run the migrations. */ public function up(): void { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->text('description'); $table->string('author'); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('posts'); } };
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('description');
            $table->string('author');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

Update the database settings .env then run migrate

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php artisan migrate
php artisan migrate
php artisan migrate

 

Submitting The Form

Inertia provide us the router module which enable us to submit forms. The inertia router contains various methods to handle CRUD operations like post, put, delete, update:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
router.post('/posts', data);
router.put('/posts', data);
router.patch('/posts', data);
router.get('/posts/1');
router.delete('/posts/1');
router.post('/posts', data); router.put('/posts', data); router.patch('/posts', data); router.get('/posts/1'); router.delete('/posts/1');
router.post('/posts', data);
router.put('/posts', data);
router.patch('/posts', data);
router.get('/posts/1');
router.delete('/posts/1');

 

To start submitting the form first import the inertia router for inertia vue 3 like so:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { reactive } from 'vue'
import { router } from '@inertiajs/vue3'
import { reactive } from 'vue' import { router } from '@inertiajs/vue3'
import { reactive } from 'vue'
import { router } from '@inertiajs/vue3'

Then inside of the save() function we call router.post()

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function save() {
router.post('/posts', form);
}
function save() { router.post('/posts', form); }
 function save() {
        router.post('/posts', form);
}

The router.post() function accepts the route url as the first argument which already defined in web.php and payload as the second argument.

Let’s update the PostsController::store() method to capture the request data, validate and save.

import the Post model at the top:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\Post;
...
...
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Inertia\Inertia; use App\Models\Post; ... ...
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\Post;

...
...
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public function store(Request $request)
{
$this->validate($request, [
'title' => 'bail|required|string|min:4',
'description' => 'bail|required|string',
'author' => 'bail|required|string|min:3'
]);
$post = new Post();
$post->title = $request->title;
$post->description = $request->description;
$post->author = $request->author;
$post->save();
return redirect()->back()->with('success', 'Post created successfully');
}
public function store(Request $request) { $this->validate($request, [ 'title' => 'bail|required|string|min:4', 'description' => 'bail|required|string', 'author' => 'bail|required|string|min:3' ]); $post = new Post(); $post->title = $request->title; $post->description = $request->description; $post->author = $request->author; $post->save(); return redirect()->back()->with('success', 'Post created successfully'); }
public function store(Request $request)
    {
        $this->validate($request, [
           'title' => 'bail|required|string|min:4',
           'description' => 'bail|required|string',
           'author' => 'bail|required|string|min:3'
        ]);

        $post = new Post();
        $post->title = $request->title;
        $post->description = $request->description;
        $post->author = $request->author;
        $post->save();

        return redirect()->back()->with('success', 'Post created successfully');
    }

I validated the incoming data using the usual laravel way. In normal frontend apps sometimes we have to return a JSON response as we were dealing with ajax XHR requests, but inertia help us remove this burden.

Next i saved the data in the eloquent model Post and i redirect back with a success message.

 

Displaying validation errors

At this point let’s display the errors on the Vue form. To accomplish Inertia uses special prop called “errors” available in page.props.errors object, so all we have to do is to declare this prop and check for the existence any related property in the validation bag.

In Pages/create-post.vue add this line:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
defineProps({ errors: Object });
defineProps({ errors: Object });
defineProps({ errors: Object });

After import { router } from ‘@inertiajs/vue3’

Now we have errors prop in place let’s update the form and add a div below each input to check for any validation property in errors like so:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<form method="post" @submit.prevent="save" class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
<div class="mb-4">
<label class="block text-gray-700 text-sm font-bold mb-2">Title</label>
<input type="text" name="title" v-model="form.title" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700" />
<div v-if="errors.title" class="text-red-600 mt-2 text-sm">{{ errors.title }}</div>
</div>
<div class="mb-4">
<label class="block text-gray-700 text-sm font-bold mb-2">Description</label>
<textarea name="description" v-model="form.description" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700"></textarea>
<div v-if="errors.description" class="text-red-600 mt-2 text-sm">{{ errors.description }}</div>
</div>
<div class="mb-4">
<label class="block text-gray-700 text-sm font-bold mb-2">Author</label>
<input type="text" name="author" v-model="form.author" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700" />
<div v-if="errors.author" class="text-red-600 mt-2 text-sm">{{ errors.author }}</div>
</div>
<div>
<button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">Save</button>
</div>
</form>
<form method="post" @submit.prevent="save" class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"> <div class="mb-4"> <label class="block text-gray-700 text-sm font-bold mb-2">Title</label> <input type="text" name="title" v-model="form.title" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700" /> <div v-if="errors.title" class="text-red-600 mt-2 text-sm">{{ errors.title }}</div> </div> <div class="mb-4"> <label class="block text-gray-700 text-sm font-bold mb-2">Description</label> <textarea name="description" v-model="form.description" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700"></textarea> <div v-if="errors.description" class="text-red-600 mt-2 text-sm">{{ errors.description }}</div> </div> <div class="mb-4"> <label class="block text-gray-700 text-sm font-bold mb-2">Author</label> <input type="text" name="author" v-model="form.author" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700" /> <div v-if="errors.author" class="text-red-600 mt-2 text-sm">{{ errors.author }}</div> </div> <div> <button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">Save</button> </div> </form>
<form method="post" @submit.prevent="save" class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
        <div class="mb-4">
            <label class="block text-gray-700 text-sm font-bold mb-2">Title</label>
            <input type="text" name="title" v-model="form.title" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700" />
            <div v-if="errors.title" class="text-red-600 mt-2 text-sm">{{ errors.title }}</div>
        </div>
        <div class="mb-4">
            <label class="block text-gray-700 text-sm font-bold mb-2">Description</label>
            <textarea name="description" v-model="form.description" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700"></textarea>
            <div v-if="errors.description" class="text-red-600 mt-2 text-sm">{{ errors.description }}</div>
        </div>
        <div class="mb-4">
            <label class="block text-gray-700 text-sm font-bold mb-2">Author</label>
            <input type="text" name="author" v-model="form.author" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700" />
            <div v-if="errors.author" class="text-red-600 mt-2 text-sm">{{ errors.author }}</div>
        </div>
        <div>
            <button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">Save</button>
        </div>
    </form>

As shown the three div’s to check for errors like below:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<div v-if="errors.title" class="text-red-600 mt-2 text-sm">{{ errors.title }}</div>
<div v-if="errors.title" class="text-red-600 mt-2 text-sm">{{ errors.title }}</div>
<div v-if="errors.title" class="text-red-600 mt-2 text-sm">{{ errors.title }}</div>

Check the form now and click submit you will notice the errors appears in case of invalid or required data.

 

Displaying success message and reset the form

In the the above Controller in the store() method after saving the data we redirect back with a flash message. We need to display this message in the top of the form. Inertia enable us to do so using shared data.

Open app/Http/Middleware/HandleInertiaRequests.php

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public function share(Request $request): array
{
return array_merge(parent::share($request), [
'flash' => [
'success' => fn () => $request->session()->get('success')
],
]);
}
public function share(Request $request): array { return array_merge(parent::share($request), [ 'flash' => [ 'success' => fn () => $request->session()->get('success') ], ]); }
public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            'flash' => [
                'success' => fn () => $request->session()->get('success')
            ],
        ]);
    }

The share() method shares data across multiple pages. For flash messages add all the flash keys your application using under the ‘flash‘ key. In this case we have one key ‘success’ that is returned in the store() method.

You might have key for error and key for warning:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
return array_merge(parent::share($request), [
'flash' => [
'success' => fn () => $request->session()->get('success'),
'error' => fn () => $request->session()->get('error'),
'warning' => fn () => $request->session()->get('warning')
],
]);
return array_merge(parent::share($request), [ 'flash' => [ 'success' => fn () => $request->session()->get('success'), 'error' => fn () => $request->session()->get('error'), 'warning' => fn () => $request->session()->get('warning') ], ]);
return array_merge(parent::share($request), [
            'flash' => [
                'success' => fn () => $request->session()->get('success'),
                'error' => fn () => $request->session()->get('error'),
                'warning' => fn () => $request->session()->get('warning')
            ],
        ]);

After that display this flash message on the Vue app by checking for $page.props.flash.success

In Pages/create-post.vue add this div before the <form /> tag

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<div class="bg-green-400 p-3" v-if="$page.props.flash.success">
{{ $page.props.flash.success }}
</div>
<div class="bg-green-400 p-3" v-if="$page.props.flash.success"> {{ $page.props.flash.success }} </div>
<div class="bg-green-400 p-3" v-if="$page.props.flash.success">
        {{ $page.props.flash.success }}
    </div>

The flash message is showing now. The second step is to reset the form. Here in inertia we need a way to tell if the form submission is successful.

This can be done using some events given by Inertia provided in the router module:

Add this code after the save() function:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
router.on('success', (event) => {
form.title = '';
form.description = '';
form.author = '';
});
router.on('success', (event) => { form.title = ''; form.description = ''; form.author = ''; });
router.on('success', (event) => {
        form.title = '';
        form.description = '';
        form.author = '';
    });

There is also other events like:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
router.on('error', (errors) => {
})
router.on('error', (errors) => { })
router.on('error', (errors) => {

})
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
router.on('invalid', (event) => {
console.log(`An invalid Inertia response was received.`)
})
router.on('invalid', (event) => { console.log(`An invalid Inertia response was received.`) })
router.on('invalid', (event) => {
  console.log(`An invalid Inertia response was received.`)

})
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
router.on('exception', (event) => {
console.log(`An unexpected error occurred during an Inertia visit.`)
})
router.on('exception', (event) => { console.log(`An unexpected error occurred during an Inertia visit.`) })
router.on('exception', (event) => {
  console.log(`An unexpected error occurred during an Inertia visit.`)
})

Code Repository

 

0 0 votes
Article Rating

What's your reaction?

Excited
0
Happy
0
Not Sure
0
Confused
0

You may also like

Subscribe
Notify of
guest


0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments