Sacha::corazzi();

Laravel Precognition: Beyond the frontend

Precognitive requests were introduced with Laravel 9 and overhauled with the release of Laravel 10.

The main use case for precognitive requests is to validate forms before the user submits them, allowing you to keep your application’s validation logic entirely in the backend. This is a great way to keep your code dry and avoid having to maintain two sets of validation logic.

This is great if you’re using a frontend framework like Vue or React, but the use cases go far beyond that.

At the time of writing, the Laravel docs for precognitive requests explains how to install ready-made frontend helpers, but doesn’t go into detail on how to implement precognitive requests from scratch.

Turns out, it’s pretty simple. All you have to do is add this header to your request:

Precognition: true

This header tells Laravel that this is a precognitive request. Laravel will then return a 422 response with the validation errors, if there are any, or a 200 if the request is valid.

You can also add a second header to let Laravel know which specific fields should be validated:

Precognition-Validate-Only: name,email,address

Now we know how to send precognitive requests, let’s look at some use cases.

Beyond the Frontend

My first thought when I heard about precognitive requests was that they would be great for mobile applications, or really anything that consumes your Laravel API.

At my job, we have apps that send up data including a photo. We have a number of checks in place on the backend to ensure that the data is valid and that the structure is correct for the what is being sent up. There are also authentication and authorisation checks to ensure that the user is allowed to perform the action.

The photo is a large file and can take a while to upload, especially in settings where bandwidth is at a premium, locations with bad Wi-Fi, or regions with low internet speeds. What if we could validate the rest of the data before the user uploads the photo? This would save the user time and bandwidth, and could also potentially save us money on our server costs.

The app would send up a preflight request to validate the data, and then send up the full request with the photo if the validation and auth checks pass. Here’s an example of how that might be done in Swift:

func preflightValidation(photoData: PhotoData) {
    // Build the request
    let request = Request.Builder()
        .url("https://example.com/api/upload")
        .addHeader("Precognition", true)
        .addHeader("Precognition-Validate-Only", "qr_code,title,location") // Optional
        .post(photoData)
        .build()

    // Send it and, if it was successful, send the full request
    // ...
}

The request would be sent to the same route as the full request, but Laravel would know that it’s a precognitive request because of the Precognition header. In this case, we’d normally be sending up a photo with the request, but for the validation check we want to let Laravel know to exclude this from the validation rules.

In your FormRequest, you can use the isPrecognitive method to conditionally apply validation rules:

protected function rules()
{
    return [
        // ...
        'image' => [
            $this->isPrecognitive() ? [] : ['required'],
        ],
    ];
}

The precognitive request will hit every point – middleware, auth, validation, etc. – that the full request would hit, but stops before hitting the actual controller where your logic is.

You should bear this in mind when using precognitive requests and be weary of side effects. For example, if you’re using a middleware that writes to the database or a cache, you might want to re-think what happens for precognitive requests.