{"id":275,"date":"2026-05-30T21:38:25","date_gmt":"2026-05-31T04:38:25","guid":{"rendered":"https:\/\/www.mariatech.com.mx\/blog\/?p=275"},"modified":"2026-05-30T21:38:26","modified_gmt":"2026-05-31T04:38:26","slug":"como-usar-form-request-validation-para-formularios-enviados-con-ajax","status":"publish","type":"post","link":"https:\/\/www.mariatech.com.mx\/blog\/laravel-php\/como-usar-form-request-validation-para-formularios-enviados-con-ajax\/","title":{"rendered":"C\u00f3mo usar Form Request Validation para formularios enviados con Ajax"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Cuando trabajamos con formularios en Laravel, lo m\u00e1s com\u00fan es validar la informaci\u00f3n usando <strong>Form Requests<\/strong>. Esto permite mantener los controladores limpios y centralizar las reglas de validaci\u00f3n en clases independientes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sin embargo, cuando el formulario se env\u00eda por medio de <strong>Ajax<\/strong>, especialmente desde modales o componentes din\u00e1micos, surge una duda com\u00fan: \u00bfc\u00f3mo devolvemos los errores de validaci\u00f3n en formato JSON sin dejar de usar la estructura nativa de Laravel?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En este art\u00edculo veremos una forma pr\u00e1ctica de trabajar con <strong>Request validation<\/strong> con formularios Ajax, respuestas JSON y c\u00f3digos HTTP correctos.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">El problema com\u00fan<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">En un formulario tradicional, si la validaci\u00f3n falla, Laravel redirige autom\u00e1ticamente al usuario a la p\u00e1gina anterior y muestra los errores en la vista.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pero cuando usamos Ajax, no queremos una redirecci\u00f3n. Queremos una respuesta JSON que podamos procesar desde JavaScript para mostrar los errores dentro de un modal, un toast o una alerta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Una soluci\u00f3n r\u00e1pida suele ser hacer la validaci\u00f3n directamente en el controlador:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$validator = Validator::make($request-&gt;all(), $rules, $messages);\n\nif ($validator-&gt;fails()) {\n    return response()-&gt;json(&#91;\n        'success' =&gt; false,\n        'errors' =&gt; $validator-&gt;errors()-&gt;all(),\n    ], 422);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Aunque esto funciona, tiene una desventaja importante: la validaci\u00f3n queda mezclada dentro del controlador. Conforme el proyecto crece, esto vuelve el c\u00f3digo m\u00e1s dif\u00edcil de mantener.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">La mejor opci\u00f3n: seguir usando Form Request<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Laravel permite seguir usando Form Requests aun cuando el formulario se env\u00eda por Ajax. Lo \u00fanico que necesitamos es personalizar la respuesta cuando la validaci\u00f3n falla.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Primero, podemos crear un Form Request:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>php artisan make:request Admin\/User\/StoreUserRequest<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Luego definimos las reglas de validaci\u00f3n:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>namespace App\\Http\\Requests\\Admin\\User;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Rule;\n\nclass StoreUserRequest extends FormRequest\n{\n    public function authorize(): bool\n    {\n        return true;\n    }\n\n    public function rules(): array\n    {\n        return &#91;\n            'name' => &#91;'required', 'string', 'max:255'],\n            'last_name' => &#91;'required', 'string', 'max:255'],\n            'email' => &#91;'required', 'email', 'max:255', Rule::unique('users', 'email')],\n            'phone' => &#91;'nullable', 'digits:10'],\n            'id_profile_type' => &#91;'required', 'exists:profile_types,id'],\n            'password' => &#91;'required', 'string', 'min:8', 'confirmed'],\n            'enabled' => &#91;'nullable', 'boolean'],\n        ];\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Personalizar la respuesta JSON de validaci\u00f3n<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Por defecto, Laravel devuelve los errores agrupados por campo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n    \"message\": \"The given data was invalid.\",\n    \"errors\": {\n        \"email\": &#91;\n            \"Este correo electr\u00f3nico ya est\u00e1 registrado.\"\n        ],\n        \"password\": &#91;\n            \"La contrase\u00f1a debe tener al menos 8 caracteres.\"\n        ]\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Este formato es \u00fatil cuando queremos marcar errores campo por campo. Pero si nuestra interfaz usa modales y queremos mostrar una lista general de errores, puede ser m\u00e1s c\u00f3modo devolver una lista plana.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para eso podemos sobrescribir el m\u00e9todo <code>failedValidation<\/code> dentro del StoreUserRequest:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>use Illuminate\\Contracts\\Validation\\Validator;\nuse Illuminate\\Http\\Exceptions\\HttpResponseException;\n\nprotected function failedValidation(Validator $validator): void\n{\n    throw new HttpResponseException(response()-&gt;json(&#91;\n        'success' =&gt; false,\n        'message' =&gt; 'Hay errores en el formulario.',\n        'errors' =&gt; $validator-&gt;errors()-&gt;all(),\n    ], 422));\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Con esto, la respuesta quedar\u00e1 as\u00ed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n    \"success\": false,\n    \"message\": \"Hay errores en el formulario.\",\n    \"errors\": &#91;\n        \"El nombre es obligatorio.\",\n        \"Este correo electr\u00f3nico ya est\u00e1 registrado.\",\n        \"La contrase\u00f1a debe tener al menos 8 caracteres.\"\n    ]\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Controlador limpio usando el Form Request<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Una vez que el Form Request est\u00e1 listo, el controlador puede mantenerse limpio:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>use App\\Http\\Requests\\Admin\\User\\StoreUserRequest;\nuse App\\Models\\User;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Support\\Facades\\Hash;\nuse Throwable;\n\npublic function store(StoreUserRequest $request): JsonResponse\n{\n    try {\n        $validated = $request-&gt;validated();\n\n        $user = User::create(&#91;\n            'name' =&gt; $validated&#91;'name'],\n            'last_name' =&gt; $validated&#91;'last_name'],\n            'email' =&gt; $validated&#91;'email'],\n            'phone' =&gt; $validated&#91;'phone'] ?? null,\n            'id_profile_type' =&gt; $validated&#91;'id_profile_type'],\n            'password' =&gt; Hash::make($validated&#91;'password']),\n            'enabled' =&gt; $validated&#91;'enabled'] ?? 1,\n        ]);\n\n        return response()-&gt;json(&#91;\n            'success' =&gt; true,\n            'message' =&gt; 'Usuario registrado correctamente.',\n            'user_id' =&gt; $user-&gt;id,\n        ], 201);\n\n    } catch (Throwable $e) {\n        report($e);\n\n        return response()-&gt;json(&#91;\n            'success' =&gt; false,\n            'message' =&gt; 'No fue posible registrar el usuario. Int\u00e9ntalo nuevamente.',\n        ], 500);\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">C\u00f3digos HTTP recomendados<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Es importante regresar c\u00f3digos HTTP correctos para que el frontend pueda interpretar bien la respuesta.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>200:<\/strong> operaci\u00f3n correcta.<\/li>\n\n\n\n<li><strong>201:<\/strong> registro creado correctamente.<\/li>\n\n\n\n<li><strong>401:<\/strong> usuario no autenticado.<\/li>\n\n\n\n<li><strong>403:<\/strong> usuario autenticado, pero sin permisos.<\/li>\n\n\n\n<li><strong>404:<\/strong> recurso no encontrado.<\/li>\n\n\n\n<li><strong>422:<\/strong> error de validaci\u00f3n.<\/li>\n\n\n\n<li><strong>500:<\/strong> error inesperado del servidor.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Para formularios Ajax, una convenci\u00f3n pr\u00e1ctica ser\u00eda:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Validaci\u00f3n fallida  \u2192 422\nCreaci\u00f3n correcta   \u2192 201\nActualizaci\u00f3n       \u2192 200\nEliminaci\u00f3n         \u2192 200\nNo encontrado       \u2192 404\nSin permisos        \u2192 403\nError inesperado    \u2192 500<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusi\u00f3n<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Usar Form Requests con formularios Ajax es una forma limpia y profesional de validar datos en Laravel sin sacrificar la experiencia de usuario.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La clave est\u00e1 en no llevar las reglas de validaci\u00f3n al controlador, sino mantenerlas dentro del Request y personalizar la respuesta JSON cuando la validaci\u00f3n falla.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Con esta estructura logramos:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Controladores m\u00e1s limpios.<\/li>\n\n\n\n<li>Validaciones reutilizables.<\/li>\n\n\n\n<li>Respuestas JSON consistentes.<\/li>\n\n\n\n<li>C\u00f3digos HTTP correctos.<\/li>\n\n\n\n<li>Mejor integraci\u00f3n con formularios en modales.<\/li>\n\n\n\n<li>Mejor experiencia de usuario usando Ajax.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Esta forma de trabajo es especialmente \u00fatil en paneles administrativos, sistemas internos, CRMs, ERPs y plataformas donde los formularios se muestran en modales o secciones din\u00e1micas.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Si necesitas ayuda para crear un proyecto de software en\u00a0<strong>Maria Tech\u00a0<\/strong>contamos con los expertos para apoyarte.<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-794e3cfa wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/wa.me\/523414305984\" target=\"_blank\" rel=\"noreferrer noopener\">(+52) 341-430-59-84<\/a><\/div>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"mailto:info@mariatech.com.mx\" target=\"_blank\" rel=\"noreferrer noopener\">info@mariatech.com.mx<\/a><\/div>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>En este art\u00edculo veremos una forma pr\u00e1ctica de trabajar con Request validation con formularios Ajax&#8230;<\/p>\n","protected":false},"author":1,"featured_media":276,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6],"tags":[],"class_list":["post-275","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-laravel-php"],"_links":{"self":[{"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/posts\/275","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/comments?post=275"}],"version-history":[{"count":0,"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/posts\/275\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/media\/276"}],"wp:attachment":[{"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/media?parent=275"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/categories?post=275"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/tags?post=275"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}