Form validation: the rules that live on both sides of the request
A form is the one place where your application and your user argue. Validation decides whether that argument ends with submitted data or with the user closing the tab. It looks like the simplest feature in any app and produces a surprising share of support tickets.
The core rule takes one sentence: client-side validation is for the user, server-side validation is for your data. Everything else in this post follows from it.
Client-side checks are a courtesy, not a defense
Disabling JavaScript, editing the request in a proxy, or posting with curl bypasses every browser-side rule. Any data that matters needs the same rules enforced again on the server, against the request as it actually arrived. In Laravel that means Form Requests with their own rules, not inline checks in controllers.
The common shortcut is duplicating rules in two places and hoping they stay in sync. It's faster to accept the duplication and test it than to build clever plumbing that shares rules between two runtimes. The rules will drift eventually; a test that submits an invalid payload straight to the endpoint catches the drift when it happens.
Errors should say what to fix, where
A message like "Invalid input" at the top of the page makes the user hunt. Each error belongs next to the field that caused it, in a sentence that names the problem: "Enter a phone number in +371 2XX XXXX format" rather than "invalid phone."
Laravel's validation already returns errors keyed by field name. The frontend job is to surface them per-field and keep everything else the user typed. Nothing trains users to hate a form faster than an error that clears twelve filled fields.
Validate types and boundaries, not vibes
Rules that look permissive usually come back as production bugs and abuse vectors. Worth being strict about:
- Strings have a maximum length at the database, and validation should enforce it before the database throws
- Numbers have ranges: a negative discount passes every "is numeric" check and still wrecks the invoice
- Dates get compared to other dates: an end date before the start date is a classic
- Files get checked by content and size, not by their filename's extension
- Everything optional is explicitly optional, everything else required
None of this is exotic. It's the list of fields your support team ends up describing to users one ticket at a time.
The mistakes users see
Three patterns account for most form frustration in the wild:
- Losing input on error. The form re-renders empty and the user retypes everything. Server-side errors belong in the same form state the user left behind.
- Aggressive real-time validation. An email field marked invalid after three keystrokes, before the user finishes typing. Validate on blur or submit, not mid-word.
- No confirmation of success. The submit button spins and nothing obviously happens. Success needs the same visibility as failure: a state change, a redirect, something unmissable.
Each one is a line-level fix. Together they decide whether people finish signing up.
Error responses for API clients
The same thinking applies when the form is an app calling your JSON API. Consistent, structured errors (422 with a field-keyed body, in Laravel's case) let every client render errors the same way. A raw 500 on invalid input is a validation gap wearing a server error costume; if a bad payload can trigger it, the validation missed a case.
Testing the boring paths
Validation bugs are cheap to catch in tests and embarrassing to catch in production. The set worth having:
- Each required field rejected when empty, in one parameterized test
- Boundary cases at the limits you defined (max length, max upload size)
- The happy path passing with valid data
- One test submitting deliberately invalid JSON straight to the endpoint, bypassing the frontend
That last test is the one teams skip. It's also the one that catches the drift between client and server rules, and it doubles as your regression net when someone "simplifies" a Form Request next year.
Validation done well is invisible: the user fills the form, it works, and they never think about it again. The way to get there is enforcing the rules where they can't be bypassed and communicating failures like a colleague would, not like a stack trace.