Validating Input
Making sure bad data does not reach the database
Concepts
- Input validation checks that data from outside the
application meets requirements before processing it
- data that passes through without validation can corrupt the database or produce incorrect results
- Type-based validation: when a Litestar handler receives a request,
it maps the body to a dataclass
- if a field has type
floatbut the value is"hello", Litestar automatically returns a 400 response before the handler runs
- if a field has type
- Custom validators enforce rules that types cannot express: value ranges, referential integrity, and business rules
- Defense in depth means validating at multiple layers:
HTML5 attributes in the browser, type checks in the framework,
custom rules in the application, and constraints in the database
- no single layer is sufficient on its own
- When returning a validation error, preserve the user's input and
place error messages next to the affected fields
- the user knows exactly what to fix without re-entering everything
Why validation matters
- Concrete examples: a negative shell diameter, a future date, a site name that does not exist
- What happens without validation
Litestar's built-in validation
- Declaring a dataclass with type annotations and letting Litestar reject malformed requests automatically
- What the 400 error response looks like
Custom validation
- Checks that type annotations cannot express: range constraints, referential integrity, business rules
- A validator function and where to put it
Returning validation errors to the user
- Displaying error messages next to the relevant field
- Using HTMX to show the error without losing the user's input
Check for Understanding
What kinds of invalid input can Litestar's type-based validation catch automatically, and what kinds does it miss?
Type-based validation catches inputs that cannot be converted to the declared type
(e.g., a string where a float is expected) and missing required fields. It cannot
catch inputs that are the right type but the wrong value: a negative diameter is still
a float, a future date is still a valid date, and a nonexistent site name is still
a str. Custom validators are needed for those.
What is referential integrity, and why does a type annotation alone not enforce it?
Referential integrity means that a value in one table must correspond to an existing row in another table (e.g., the site name in a measurement must appear in the sites table). A type annotation can only say "this field is a string"; it has no way to check whether that string matches any row in the database. Enforcing referential integrity requires either a SQL foreign key constraint or a custom validator that queries the database.
Why is client-side (browser) validation not enough, even if it catches most mistakes?
A user can disable JavaScript, use browser developer tools to remove HTML5 attributes,
or send an HTTP request directly with curl or a script, bypassing the browser
entirely. Client-side validation is a convenience for honest users; server-side
validation is the security boundary. Both are needed, but server-side validation must
never be skipped.
If the server returns a validation error, how do you display the error message next to the right form field without losing the user's other inputs?
The server returns an HTML fragment (via HTMX) that contains the form with its fields pre-filled from the submitted values, plus error messages inserted next to the invalid fields. The HTMX response replaces the form element in the page, so the user sees their input intact and the error highlighted in context. A full-page redirect would lose all the filled-in values.
Exercises
Add range validation
Add validation that shell diameter must be between 0.1 mm and 50 mm. Before writing
code, decide where this check belongs: HTML5 min/max attributes, a Litestar type
annotation, a custom validator, a database constraint, or some combination. Write a
one-paragraph justification, implement the chosen approach, and test it with values
at the boundary (0.1, 50) and outside (0, 50.1).
Validate the site name
Add a custom validator that checks whether the submitted site name exists in the sites table. Write the validator function before asking the LLM. Compare your implementation to what the LLM produces. Note whether the LLM handles the case where the database connection is not available.
Test invalid inputs
Write three pytest tests that send invalid data to the add-record route: one with a missing required field, one with a diameter outside the valid range, and one with a site name that does not exist in the database. Assert that each test returns a 400 status code with a response body that mentions the specific field that failed.
Improve error messages
The default Litestar validation error messages are written for developers, not researchers. Find a way to replace or supplement them with plain-language messages suitable for a non-programmer. Ask the LLM for options, implement one approach, and verify the messages are clear to someone who has not read any of this tutorial.