<?php
namespace App\Http\Requests;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Validation\Rules\Password;
class RegisterUserRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
protected function prepareForValidation(): void
{
$this->merge([
'email' => strtolower(trim((string) $this->input('email'))),
'name' => trim((string) $this->input('name')),
]);
}
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email:rfc', 'max:255', 'unique:users,email'],
'password' => [
'required',
'confirmed',
Password::min(10)->mixedCase()->numbers()->symbols(),
],
];
}
public function messages(): array
{
return [
'email.unique' => 'An account with that email already exists.',
'password.confirmed' => 'The password confirmation does not match.',
];
}
protected function failedValidation(Validator $validator): void
{
throw new HttpResponseException(response()->json([
'message' => 'The given data was invalid.',
'errors' => $validator->errors(),
], 422));
}
}
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\RegisterUserRequest;
use App\Http\Resources\UserResource;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Hash;
class RegisteredUserController extends Controller
{
public function store(RegisterUserRequest $request): JsonResponse
{
$data = $request->validated();
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
return (new UserResource($user))
->response()
->setStatusCode(201);
}
}
<?php
use App\Http\Controllers\Auth\RegisteredUserController;
use Illuminate\Support\Facades\Route;
Route::post('/register', [RegisteredUserController::class, 'store'])
->middleware('throttle:6,1')
->name('register');
This snippet shows how Laravel's Form Request classes centralize input validation for a signup flow, keeping controllers thin while still returning consistent error payloads to both web and API clients. The core idea is that validation, authorization, and normalization all live in a dedicated request object that runs before the controller action executes.
In RegisterUserRequest, the authorize method returns true because signup is public — but this hook exists so protected endpoints can gate access before any validation runs. The rules method declares the constraints: email must be a valid RFC address that is unique against the users table, and password uses the Password rule builder to require a minimum length, mixed case, and at least one symbol. Using confirmed on the password automatically checks for a matching password_confirmation field, which avoids hand-written comparison logic. The messages method overrides a few defaults so the errors read like product copy rather than framework jargon.
A subtle but important piece is prepareForValidation, which runs before the rules are applied. It lowercases and trims the email so uniqueness checks and later lookups behave predictably — normalizing input at the boundary prevents duplicate accounts that differ only by casing or stray whitespace. The failedValidation override is what makes this work cleanly for JSON APIs: instead of Laravel's default redirect-with-session-errors behavior, it throws an HttpResponseException carrying a structured 422 body, so front-end clients always receive machine-readable errors under an errors key.
In RegisteredUserController, the action type-hints RegisterUserRequest. Laravel resolves and runs the request through the service container, so by the time store executes, the data is guaranteed valid — $request->validated() returns only the whitelisted, sanitized fields. This is safer than pulling raw input because it prevents mass-assignment of unexpected keys. The password is hashed with Hash::make before persistence, and a 201 response is returned with the created resource.
The trade-off is indirection: logic moves out of the controller into a class that must be discovered. In exchange, validation becomes reusable, testable in isolation, and consistent across every entry point that reuses the request. Reach for this pattern whenever an endpoint accepts non-trivial user input.
Related snips
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: String!
GraphQL API with Spring Boot
class SignupForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :account_name, :string
attribute :email, :string
Shallow Controller, Deep Params: Form Object Pattern
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["form"]
static values = { delay: { type: Number, default: 250 } }
Debounced live search with Stimulus + Turbo Streams
#!/usr/bin/env bash
set -euo pipefail
export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}"
Secrets management with environment isolation and Vault
Share this code
Here's the card — post it anywhere.