Catching Laravel Validation Errors in Vue

Catching Laravel Validation Errors in Vue

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!

Filed in: News

Newsletter

Join 31,000+ others and never miss out on new tips, tutorials, and more.

Laravel News Partners

Laravel Jobs

Laravel Senior Developer
Remote, Canada Only
BeMo Academic Consulting
Senior Fullstack Developer / Architect (w/m/d)
Remote / Munich - Germany
envivo.select GmbH
Medior full stack developer (Laravel)
Deventer (or remote) in the Netherlands, Dutch speaking required.
MSML B.V.
Senior Full Stack PHP Developer (Laravel)
Remote
MAPPEN
Senior Laravel Developer
Remote
ACTO