Server-side safety
Build a complete feedback form
Combine accessible HTML, browser validation, FormData, HTTP handling, server validation, security controls, and useful result states.
Time to put the whole course into one form: a feedback form that works from the browser to the server and behaves well when things go wrong.
The form collects:
- name
- email address
- category
- message
- optional screenshot
Start with native HTML
Write the form as if JavaScript didn’t exist. Every control gets a visible label connected with for and id, and a stable name. The category is a radio group inside a fieldset with a legend. The message gets a help paragraph, linked with aria-describedby, stating the length limit.
Set action="/feedback" and method="post". Add enctype="multipart/form-data" for the screenshot, and required, minlength and maxlength where the contract needs them.
Submit it once with the Network panel open before writing any script. If the request looks right, everything you add later is an enhancement, not a dependency.
Define the server contract
Write down what /feedback accepts before writing the handler. Name: 1 to 80 characters. Category: one of the values in the page. Message: 20 to 2000 characters. Screenshot: optional, PNG or JPEG by content, under 5 MB.
Validate in the order from the untrusted input lesson: body size, content type, parse, required fields, lengths and allowlists, then the database. Treat the filename and media type as untrusted. Generate a storage name and keep the file outside the application directory.
On a validation failure, render the form again with field errors beside the right controls and valid values kept. On native success, answer with a 303 to a confirmation page.
Add CSRF protection if the form runs under a cookie session, plus a body-size limit and a rate limit. Don’t log the message, the token, or the upload bytes.
Add JavaScript as an enhancement
Now take over the submit event. Build a FormData with the submitter, send it with fetch() and an Accept: application/json header, and check response.ok before believing anything.
Show all the states: idle, pending, success, validation error, network error. Disable the active submitter while pending and restore it in finally. Reset only after confirmed success. Make the endpoint safe if the same request arrives twice. If the script fails to load, the native path must still work.
Break it
Complete the form with only the keyboard. Then try:
- empty and overlong values
- a category not present in the page
- a large file and a renamed non-image
- a missing or invalid CSRF token when required
- repeated submission
- offline mode and a server error
- refresh after success
- 200% zoom and narrow screens
You are done when each of those produces a message a person can understand, keeps their work, and leaves the form usable.
Lesson completed