Forms
Sending data to the server
Concepts
- An HTML form collects data from the user and sends it to the server when submitted
- The
<form>element wraps one or more inputs and a submit button - Each input has a
nameattribute that becomes the key in the submitted data - What happens if two or more inputs have the same name?
- The
- The HTTP method tells the server what kind of action is being requested
GETasks the server to return something without changing any statePOSTtells the server to accept data and change something as a result- A search belongs on GET because it does not change the database; adding a record belongs on POST because it does
- If a form uses
method="post", where does the submitted data appear in the HTTP request?
- Form encoding is the format browsers use to pack field values into the request body
- The default is
application/x-www-form-urlencoded, which turns spaces into+and reserved characters into%XXsequences - File uploads require
multipart/form-datainstead - Why can't a form use the URL query string for POST data?
- The default is
- The Post/Redirect/Get pattern prevents duplicate submissions
- After a successful POST, the server sends a redirect response instead of an HTML page
- The browser follows the redirect with a GET request, so refreshing the page does not resubmit the form
- Give an example of what goes wrong if Post/Redirect/Get isn't used
- Server-side validation checks submitted data before using it
- Client-side checks in JavaScript are a convenience, not a guarantee
- Any data arriving from the network must be treated as untrusted
- Invalid input should be reported to the user, not silently discarded or stored
- Why is client-side validation alone not sufficient for a web application?
- Every form input needs a visible, programmatically associated label for accessibility
- Use
<label for="some-id">paired with<input id="some-id"> - A placeholder is not a label: it disappears when the user types
- How does a screen reader know which label goes with which input?
- Use
A form for adding a snail record
- The fields a new record needs: site name, shell diameter, body mass, and mutation flag
- Write an htpy function that returns an HTML form with inputs for site, diameter, mass,
and a checkbox for mutation, all posting to
/snails/add
- Write an htpy function that returns an HTML form with inputs for site, diameter, mass,
and a checkbox for mutation, all posting to
- Choosing the right input type for each field
- Text for site name, number for the two measurements, checkbox for the boolean
- Update the form to use
type="number"for diameter and mass andtype="checkbox"for the mutation flag
Receiving form data in Litestar
- A POST handler reads submitted fields from the request body, not the URL
- Litestar parses
application/x-www-form-urlencodedbodies automatically when the handler parameter is annotated withBody(media_type=RequestEncodingType.URL_ENCODED) - Write a Litestar route handler for
POST /snails/addthat reads the four form fields and prints them to the console
- Litestar parses
- Type coercion is your responsibility
- The body arrives as strings; diameter and mass must be converted to floats
- A missing checkbox field means
False, not an error - Update the handler to convert diameter and mass to floats and treat an absent mutation field as False
Post/Redirect/Get
- Returning HTML directly from a POST handler causes duplicate-submission problems
- The browser does not know the response is "done"; a refresh resends the POST
- Try submitting the add form and then pressing the browser's reload button. What does the browser ask before proceeding?
- Return a redirect instead of a page
- Litestar's
Redirectresponse sends a 303 status with aLocationheader - The browser follows the redirect with a GET and the user lands on a safe page
- Update the POST handler to insert the new record and then redirect to the snail listing page
- Litestar's
Validating input
- Reject values that cannot be correct before touching the database
- Diameter and mass must be positive numbers
- Site name must not be blank
- Add validation to the handler that returns an error message when any field fails these checks
- Showing error messages next to the offending field
- Re-render the form with the submitted values preserved and error text beside each bad input
- Update the form-rendering function to accept a dictionary of field errors and display each error beside the relevant input
Accessible form markup
- Pair every
<input>with a<label>using matchingforandidattributes- Add
<label>elements to the snail form so each input has a programmatically associated label
- Add
- Group related inputs with
<fieldset>and<legend>- The mutation checkbox is a yes/no question; wrapping it in a fieldset makes its purpose clear
- Wrap the mutation checkbox in a
<fieldset>with a<legend>that describes the question
- Link validation errors to their inputs with
aria-describedby- A screen reader can then announce the error immediately after the field label
- Update the form to give each error message a unique
idand addaria-describedbyto the corresponding input pointing at that id
Check for Understanding
When should a form use method="post" rather than method="get"?
Use POST when submitting the form changes something on the serve, such as adding a record, deleting data, or triggering an action. Use GET when the form only retrieves or filters data, as with a search. POST data does not appear in the URL, so it cannot be bookmarked, and browsers warn before resubmitting it on refresh.
What is the Post/Redirect/Get pattern and what problem does it solve?
After a successful POST, the server responds with a redirect (HTTP 303) pointing to a GET URL. The browser follows the redirect with a GET request, so the URL in the address bar represents a safe, bookmarkable page. If the user refreshes, the browser repeats the GET, not the POST, which means the form is not resubmitted and the record is not inserted a second time.
Why is a placeholder not a substitute for a label?
A placeholder disappears as soon as the user starts typing, so someone who pauses mid-form cannot tell what the field is for. Screen readers do not reliably announce placeholders as field names. Labels persist, are always visible, and are programmatically associated with the input, so assistive technology can announce "Site name, text field" when the input receives focus.
If a checkbox is not checked when a form is submitted, what value does the server receive for that field?
Nothing at all:
an unchecked checkbox sends no key-value pair in the request body.
The server must treat a missing field as False rather than raising an error for a missing key.
This is different from a text input with an empty value, which does appear in the body as fieldname=.
Why must you validate form data on the server even if you also validate it in the browser?
Anyone can send an HTTP request without using your form at all, bypassing every client-side check. Tools like curl or a Python script can post arbitrary data directly to your endpoint. Client-side validation is a convenience that improves the user experience; server-side validation is the only check you can rely on.
Exercises
Add a required attribute
Add the required attribute to each mandatory input in the snail form.
Submit the form with one field left blank and observe what the browser does,
then disable JavaScript in your browser's developer tools and try again.
What changed?
Why?
Preserve submitted values on error
When validation fails and the form is re-rendered, the user should not have to retype valid fields. Update the form-rendering function to pre-populate every input with the value the user submitted, keeping invalid entries visible so the user can correct just the bad ones.
Add a confirmation page
Instead of redirecting directly to the listing after a successful insert, redirect to a confirmation page that shows the record that was just added and offers a link back to the full listing. How will you pass the new record's ID to the confirmation page?
Reject duplicate site names
Before inserting a new record, query the database for any existing record with the same site name. If one exists, re-render the form with an error message on the site field. Write a test that inserts one record and then verifies that submitting a duplicate returns an error.
Try CSRF
Read about cross-site request forgery and describe how an attacker could exploit your current add-record form. You do not need to implement a fix; focus on explaining the threat model.