Laravel Custom Form Request Validation API Errors

Background

The Laravel framework comes with great API support out of the box. In addition to the API support, there's also form requests which are really great in enforcing the integrity of data coming into the API. However, if you make client applications that utilize compiled over interpreted languages, the challenge that form requests present is error messages whose structure isn't standardized.

{
  "message": "The given data was invalid.",
  "errors": {
    "email": [
      "The email must be a valid email address."
    ],
    "name": [
      "The name field is required."
    ],
    "verified": [
      "The verified field is required."
    ]
  }
}

Thus modelling JSON decoding on such a client is very difficult as you have to ensure that you can retrieve the respective error messages for each field at runtime. This would introduce a lot of checks that you have to write in the code to account for nulls then also introduce a lot of code to check for properties at runtime. In a language like Dart or Java this is a lot of work.

Solution

An error format such as the one shown below would be ideal for such languages which may need to know the values at compile time.

{
  "errors": [
    {
      "key": "email",
      "errors": [
        "The email must be a valid email address."
      ]
    },
    {
      "key": "name",
      "errors": [
        "The name field is required."
      ]
    },
    {
      "key": "verified",
      "errors": [
        "The verified field is required."
      ]
    }
  ]
}

Following such a format, you are able to have a slimmer JSON decoding setup. To achieve such a result, each form request allows you to override the default failedValidation method which is responsible for returning the errors in the default format used by the framework.

It can be overridden as follows:

public function failedValidation(Validator $validator)
    {
        $error_results = [];
        $errors = $validator->errors()->toArray();

        foreach ($errors as $key => $errors) {
            array_push(
                $error_results,
                [
                    'key' => $key,
                    'errors' => [...$errors]
                ]
            );
        }
        $response = response()->json(
            [
                "code" => 422,
                "errors" => $error_results
            ],
            422,
        );
        throw new HttpResponseException($response);
    }

The setup works as follows:

  1. $errors = $validator->errors()->toArray(); picks the errors returned by the validator that's part of the framework and converts them to an array
  2. The foreach loop runs over the errors and takes out the key which would be the value of the field under validation, e.g email, then the errors which is an array of errors for that specific field.
  3. array_push adds an array of key and errors pairs to the $error_results.
  4. response()->json constructs a response that we can throw at the end of the method.

It can be plugged in to your form request as follows:

class AuthenticateUser extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'email' => 'required|email',
            'password' => 'required|string',
            'name' => 'required|string',
            'verified' => 'required|boolean',
        ];
    }

    public function failedValidation(Validator $validator)
    {
        $error_results = [];
        $errors = $validator->errors()->toArray();

        foreach ($errors as $key => $errors) {
            array_push(
                $error_results,
                [
                    'key' => $key,
                    'errors' => [...$errors]
                ]
            );
        }
        $response = response()->json(
            [
                "code" => 422,
                "errors" => $error_results
            ],
            422,
        );
        throw new HttpResponseException($response);
    }
}

To reuse this in other form requests, you can throw this in a trait and import it as follows: FailedValidationTrait.php

namespace App\Traits;

use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;

trait FailedValidationTrait
{
    public function failedValidation(Validator $validator)
    {
        $error_results = [];
        $errors = $validator->errors()->toArray();

        foreach ($errors as $key => $errors) {
            array_push(
                $error_results,
                [
                    'key' => $key,
                    'errors' => [...$errors]
                ]
            );
        }
        $response = response()->json(
            [
                "code" => 422,
                "errors" => $error_results
            ],
            422,
        );
        throw new HttpResponseException($response);
    }
}

AuthenticateUser.php

class AuthenticateUser extends FormRequest
{
    use FailedValidationTrait;

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'email' => 'required|email',
            'password' => 'required|string',
            'name' => 'required|string',
            'verified' => 'required|boolean',
        ];
    }