Catching Laravel Validation Errors in Vue

Published on by

Catching Laravel Validation Errors in Vue image

Validation is a way to ensure that the data submitted from your front end forms or requests meets your expectations and ensures that we are storing the correct data in our database. When a form or request is submitted, before we run any logic with that data, we need to make sure it has met all of our requirements. If the data does not meet our requirements, we need to tell the user there is a problem with the data they submitted so they can fix it.

The Project

Currently, I am working on a SAAS app called SavvyHouseHunting.com – it is a platform that allows real estate agents to create and share 360 videos and images, documents, and messages with their clients. One of the key differences with this application is that it is a Vue SPA, leveraging Vue Router, so we aren’t able to use the Blade helper @errors as we would in a blade view.

In one area of the site, users of certain types are allowed to “invite” people to join their team; upon receiving the invitation, the user needs to provide some necessary user data to get started.

Getting started

First, since we don’t want users to accept the invitation more than once, when generating the invitation email we create a Signed URL like so:

$invitation = Invitation::create([
'to' => request('email'),
'user_id' => auth()->user()->id,
'type' => request('type')
]);
 
$link = URL::signedRoute('accept-invitation', ['invitation' => $invitation->id]);
Mail::to(request('email'))->send(new InvitationMail(auth()->user(), $link));

The email will fire off to the invited user, with the signed url, and when the user clicks the link in the email we receive that request and render our form. But to ensure that the signature is valid, we can use a simple abort_if method which takes a boolean, an http status code, and an optional message to send back to the user.

More Logic

Additionally, we need to lookup the invitation and make sure it hasn’t been accepted yet, there is an accepted column on the model that we mark as true later in the process. One additional caveat to allow for is in the scenario that this invited user actually already exists in our system, maybe they also belong to someone else’s team. Here is the accept method:

abort_if(!request()->hasValidSignature(), 403, 'YOU CAN\'T DO THAT');
$invitation = Invitation::find(request('invitation'));
abort_if($invitation->accepted, 403, 'That invitation has already been accepted');
 
$from = User::find($invitation->user_id);
try {
$user = User::where('email', $invitation->to)->firstOrFail();
$this->createAssociations($user, $invitation, $from);
$invitation->accepted = true;
$invitation->save();
auth()->login($user);
return redirect('/dashboard/communication');
} catch (ModelNotFoundException $m) {
return view('invitation.accept', compact('invitation'));
}

You can see we are running through a number of instances to validate against. Abort if the request doesn’t have a valid signature, lookup the invitation and abort if it has already been accepted, get the invitee’s email and try to find them in the database, if so, it’s ok, create the new associations with the inviter, mark the invitation as accepted, log them into the app and redirect to the dashboard, if not Laravel will throw a ModelNotFoundException. We can send them to our form with the invitation object.

Instantiating the component is straight-forward enough, we just need to pass the invitation as a prop:

<accept-invitation :invitation="{{$invitation}}"></accept-invitation>

Handling Laravel Errors in Vue

The Vue form is a bit long, but essentially, we are passing the invitation, into the Vue component and adding the invitation object to a hidden field, and instantiate what we expect from our user:

props: {
invitation: {
type: Object,
required: true,
},
},
data() {
return {
user: {
first_name: '',
last_name: '',
email: '',
phone_number: '',
password: '',
password_confirmation: '',
},
errors: null,
};
},

The errors object is where we will put any errors caught from the post request. I use Tailwind for all of my styling, the div looks like this:

<div v-if="errors" class="bg-red-500 text-white py-2 px-4 pr-0 rounded font-bold mb-4 shadow-lg">
<div v-for="(v, k) in errors" :key="k">
<p v-for="error in v" :key="error" class="text-sm">
{{ error }}
</p>
</div>
</div>

And finally, the post method:

methods: {
submit() {
axios
.post('accept-invitation', {
user: this.user,
invitation: this.invitation
})
.then(data => {
location.href = '/dashboard/communication';
})
.catch(e => {
this.errors = e.data.errors;
});
},
},

Let’s check out the controller method that handles this request:

Route::post('accept-invitation', 'InvitationController@submit');

Laravel Form Request Validation

For this method, since we are passing several fields to the controller let’s leverage Laravel’s Form Request, here we are using two methods: rules() and messages() like so:

public function rules()
{
return [
'user.first_name' => ['required', 'string', 'max:255', 'min:2'],
'user.last_name' => ['required', 'string', 'max:255'],
'user.phone_number' => ['required', 'max:255'],
'invitation.type' => ['required', 'string', 'max:255'],
'invitation' => ['required'],
'user.password' => ['required', 'min:6', 'confirmed'],
];
}
 
public function messages()
{
return [
'user.first_name.required' => 'Your first name is required',
'user.first_name.min' => 'Your first name must be at least 2 characters',
'user.first_name.max' => 'Your first name cannot be more than 255 characters',
'user.first_name.string' => 'Your first name must be letters only',
 
'user.last_name.required' => 'Your last name is required',
'user.last_name.min' => 'Your last name must be at least 2 characters',
'user.last_name.max' => 'Your last name cannot be more than 255 characters',
'user.last_name.string' => 'Your last name must be letters only',
 
'user.phone_number.required' => 'Your phone number is required',
'user.phone_number.max' => 'Your phone number cannot be more than 255 characters',
 
'user.password.required' => 'You must create a password',
'user.password.min' => 'Your password must be at least 6 characters long',
'user.password.confirmed' => 'Your password confirmation does not match',
];
}

Let’s test this out and see what we get if we pass a mismatched password:

Voila! We were able to parse Laravel Validation errors in our Vue form with ease!

Pro Tip!

One thing that I don’t see a lot of people doing is using the validated data in their controller, instead of the request data. Once the data is validated you can set that to a variable that ONLY contains the data that is validated as an array.

$validated = $request->validated();

Typically, I see developers validate the request and then use the request data with the assumption that since the validation passed, the data is correct. But what if you forgot to validate a field? Your validation would pass, but the data might not be any good. This approach will make your validations stronger and increase your code confidence!

Shane D Rosenthal photo

I am a technology junkie, family man, community leader, pilot and musician. Since the mid 80's I have been taking things apart to see how they work and trying to put them back together again, sometimes in better condition than before. Along the way I met the love of my life, made a family, found purpose in leading and teaching others and recently became a pilot. I am passionate about flying, sharing the world with those around me and catching some live metal shows when I can.

Looking to the future I intend to own my own airplane, remain instrument rated, continue teaching and guiding our youth, grow my YouTube channel and followers and share my passions to influence whoever I can.

Filed in:
Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

image
No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project.

Visit No Compromises
Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Lucky Media logo

Lucky Media

Bespoke software solutions built for your business. We ♥ Laravel

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
Larafast: Laravel SaaS Starter Kit logo

Larafast: Laravel SaaS Starter Kit

Larafast is a Laravel SaaS Starter Kit with ready-to-go features for Payments, Auth, Admin, Blog, SEO, and beautiful themes. Available with VILT and TALL stacks.

Larafast: Laravel SaaS Starter Kit
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector

The latest

View all →
Sort Elements with the Alpine.js Sort Plugin image

Sort Elements with the Alpine.js Sort Plugin

Read article
Anonymous Event Broadcasting in Laravel 11.5 image

Anonymous Event Broadcasting in Laravel 11.5

Read article
Microsoft Clarity Integration for Laravel image

Microsoft Clarity Integration for Laravel

Read article
Apply Dynamic Filters to Eloquent Models with the Filterable Package image

Apply Dynamic Filters to Eloquent Models with the Filterable Package

Read article
Property Hooks Get Closer to Becoming a Reality in PHP 8.4 image

Property Hooks Get Closer to Becoming a Reality in PHP 8.4

Read article
Asserting Exceptions in Laravel Tests image

Asserting Exceptions in Laravel Tests

Read article