How I can validate response with Form Request and ajax json for laravel 8

I have been using laravel for a short time, and I have been reading about good practices, I want to make an ajax json request with a validation in the form, I have seen that it is better to create a class “extend FormRequest” to better manage the information, in my case I am using validator but I don’t know how to manage it in such a way to simplify the controller code as much as possible.

Currently I have:

Controller:

public function store(Request $request)
{
    if($request->ajax()) {
        $messages = [
            'nombre.required'       => 'El campo nombre es necesario',
            'descripcion.required'  => 'El campo descripción es necesario',
        ];
        $rules = [
            'nombre' => 'required',
            'descripcion' => 'required',
            'url' => 'required|alpha_dash'
        ];
        $validator = Validator::make($request->all(), $rules, $messages);
        if ($validator->fails()) {
            return response()->json([
                'status' => 400,
                'errors' => $validator->messages()//$validator->errors()
            ]);
        } else {
            $crear = new Proyecto([
                'nombre'      => $request->nombre,
                'descripcion' => $request->descripcion,
                'url'         => $request->url
            ]);
            $crear->save();
            
            return response()->json([
                'status' => 200,
                'message' => 'Registro insertado con éxito'
            ]);

        }
    }else{
        return view("404");
    }
}

Model:

class Proyecto extends Model
{
   use HasFactory;
   protected $table = 'proyecto';
   protected $primaryKey = 'id_proyecto';
   protected $fillable = ['nombre', 'descripcion', 'url'];
   public function getRouteKeyName()
   {
       return 'url';
   }
}

js:

$('#crear_proyecto').submit(function(e){
    e.preventDefault();

    var formu = $("#crear_proyecto").serialize();

    $.ajax({
        type: "POST",
        url: "proyecto/crearproyecto",
        data: formu,
        dataType: "json",
        beforeSend:function(){
            $(".loader").show();
        },
        success: function (data) {
            $('#crear_proyecto').find('span.text-danger').remove();
            if (data.status == 400) {
                $.each(data.errors, function (key, error_value) {
                    $('#'+key).after("<span class='text-danger'>"+error_value+"</span>");
                });
                $(".loader").fadeOut("slow");
            }else{
                $(".loader").fadeOut("slow");
                alert(data.message);
                $('#crear_proyecto').trigger("reset");
                $("#nombre").focus();
                $('#modalformu').modal('toggle');
            }
        }
    });

I do not know if the code I have previously put is a bad practice or to leave it like that, what I have been seeing is to leave the controller as light as possible by creating a RequestForm, but I do not know how to adapt it with json and ajax, the idea was to do something like that:

use IlluminateFoundationHttpFormRequest;
use IlluminateContractsValidationValidator;
use IlluminateValidationValidationException;
class PostProyectoRequest 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 [
        'nombre' => 'required',
        'descripcion' => 'required',
        'url' => 'required|alpha_dash',
    ];
}

public function messages()
{
    return [
        'nombre.required'       => 'El campo nombre es necesario',
        'descripcion.required'  => 'El campo descripción es necesario'
    ];
}

}

EDIT Now I have this code in the controller:

public function store(PostProyectoRequest $request)
{
    $crear = new Proyecto([
        'nombre'      => $request->nombre,
        'descripcion' => $request->descripcion,
        'url'         => $request->url
    ]);

    $crear->save();
    return response()->json([
        'status' => 200,
        'message' => 'Registro insertado con éxito'
    ]);
}

And this y RequestForm

namespace AppHttpRequests;

use IlluminateDatabaseEloquentModel;
use IlluminateFoundationHttpFormRequest;
use IlluminateSupportFacadesValidator;

class PostProyectoRequest 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 [
         'nombre'      => 'required',
         'descripcion' => 'required',
         'url'         => 'required|alpha_dash'
     ];
 }

 public function messages()
 {
     return [
         'nombre.required'       => 'El campo nombre es necesario',
         'descripcion.required'  => 'El campo descripción es necesario'
     ];
 }
 protected function failedValidation(Validator $validator)
 {
    if($this->wantsJson())
    {
        $response = response()->json([
            'status' => 400,
            'errors' => $validator->errors()//$validator->errors()
        ]);
    }else{
        $response = redirect()
            ->route('guest.login')
            ->with('message', 'Ops! Some errors occurred')
            ->withErrors($validator);
    }

    throw (new ValidationException($validator, $response))
        ->errorBag($this->errorBag)
        ->redirectTo($this->getRedirectUrl());
}

}

Answer

So you are actually right on track and you don’t need to keep the old validation in the controller anymore. You want to do something like this.

//old 
//public function store(Request $request)

//new
public function store(PostProyectoRequest $request)
{
    $crear = new Proyecto([
        'nombre'      => $request->nombre,
        'descripcion' => $request->descripcion,
        'url'         => $request->url
    ]);
    $crear->save();


    //you can't have two responses because the first one would run only because 
    return response()->json([
        'status' => 200,
        'message' => 'Registro insertado con éxito'
    ]);
    
    //or 
    
    $response = [
        'status' => 200,
        'message' => 'Registro insertado con éxito'
    ];
    return json_encode($response);
}

So is instead of using the normal Request facade, you would use the form request PostProyectoRequest you created. By doing so, you are telling the controller to pass the request through the form request which would validate it and if any of the validation fails it, it would return a validation error and wouldn’t run the code in the controller. Read more about form requests here also read about early returns and returns in general