Web Foundations: HTML, CSS, and JavaScript
Build accessible, responsive pages and add interactivity with client-side JavaScript.
Content
Forms and Validation
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
Forms and Validation — CS50 Web Foundations Deep Dive
"A form is just a polite way of asking a user for data. Validation is making sure they didn’t give you a cat instead of a credit card." — Your slightly dramatic TA
Why this matters (building on what you already know)
You’ve already learned HTML structure and semantics — how to mark up headings, paragraphs, lists, and use semantic elements. You’ve also modeled data in SQL and learned why parameterized queries matter to prevent SQL injection. Forms are the bridge between a user’s browser and your server-side database logic: they collect the inputs that become rows in your tables, and validation is the safety guard that keeps those rows sane and secure.
If you skip validation here, your database becomes a messy house party: invalid data, injection attacks, and frustrated queries crashing into each other.
What is form validation? (Short definition)
- Client-side validation: checks done in the browser (HTML5 features or JavaScript) before data is sent.
- Server-side validation: checks performed on the server after data arrives — the only truly trustworthy validation.
Rule of thumb: Client-side validation = user convenience + early error feedback. Server-side validation = security and data integrity.
The anatomy of a form (HTML essentials)
HTML provides a compact, semantic way to declare expected inputs.
<form action="/register" method="POST">
<label for="username">Username</label>
<input id="username" name="username" type="text" required minlength="3" maxlength="20" />
<label for="email">Email</label>
<input id="email" name="email" type="email" required />
<label for="age">Age</label>
<input id="age" name="age" type="number" min="13" max="120" />
<button type="submit">Sign up</button>
</form>
Key attributes to remember:
- name — how the server receives the value (very important for SQL inserts/upserts)
- type — gives browsers context (email, number, password, etc.)
- required, minlength, maxlength, min, max, pattern — HTML5 validation constraints
- novalidate on
HTML5 Constraint Validation API (quick wins)
You don’t need JS to get basic validation. Browsers will refuse to submit forms that fail built-in constraints and show default UI. But for custom messages and nicer UX, the Constraint Validation API + setCustomValidity() is your friend.
const form = document.querySelector('form');
form.addEventListener('submit', e => {
const username = form.username.value;
if (!/^[a-z0-9_]{3,20}$/i.test(username)) {
form.username.setCustomValidity('Username must be 3–20 letters, numbers, or underscores.');
// reportValidity shows message immediately in some browsers
form.username.reportValidity();
e.preventDefault();
} else {
form.username.setCustomValidity(''); // clear previous error
}
});
Micro explanation: setCustomValidity assigns a validation error string; checkValidity() returns false if any field has an error.
Client-side JavaScript validation patterns (UX-focused)
Good validation does three things:
- Stop obvious mistakes early (typos, missing fields)
- Give clear, accessible feedback tied to the input
- Don’t be the only line of defense (server must re-validate)
Example: progressively validating and showing inline errors
form.addEventListener('input', e => {
const el = e.target;
if (el.name === 'email') {
el.setCustomValidity('');
if (!el.checkValidity()) {
el.setCustomValidity('Please enter a valid email address.');
}
}
});
Accessibility tip: Attach error messages with aria-describedby and use aria-invalid="true" on invalid fields so screen readers announce problems.
Regular expressions — use carefully
Regex are powerful for patterns like usernames or phone numbers. But don’t be overzealous: over-restrictive regex can frustrate users (e.g., blocking valid international phone formats).
Example safe-ish username pattern:
<input pattern="^[A-Za-z0-9_]{3,20}$" title="3–20 letters, numbers, or underscores" />
Micro explanation: Keep messages friendly and explain why something failed.
Server-side validation — non-negotiable
Never trust client data. Re-validate everything on the server:
- Required fields
- Types and ranges (e.g., age is an integer between 13 and 120)
- String lengths and formats
- Business rules (unique email, sufficient inventory, etc.)
Also: sanitize or parameterize before sending to SQL. This ties directly to the SQL injection topic you recently covered. Even if a browser validated an input as an email, a malicious user can bypass JS and send anything. Always use prepared statements / parameterized queries on the server.
Example (Node + SQLite pseudocode):
// BAD (do not do this):
const query = `INSERT INTO users (username, email) VALUES ('${username}', '${email}')`;
// GOOD:
db.run('INSERT INTO users (username, email) VALUES (?, ?)', [username, email]);
Security checklist (forms & validation)
- Validate on the server for every field
- Use parameterized SQL queries to prevent injection
- Escape output when redisplaying user content in HTML
- Rate-limit endpoints to reduce abuse
- Use CSRF tokens for state-changing POST routes
- Avoid leaking internal errors to users
Accessibility checklist (don’t forget users)
- Use <label for=> for every input
- Use role/aria attributes for custom widgets
- Link errors with aria-describedby
- Make interactive elements keyboard-navigable
- Ensure color isn’t the only indicator of error
Example workflow — from user to database
- User types into form (client-side constraints catch many mistakes)
- JS validates complex rules and shows inline errors
- If valid, browser sends POST to server
- Server runs strict validation and returns precise errors on failure
- Server uses parameterized queries to insert into DB
- Server returns success or error message, client updates UI
This two-layer validation is like having a friendly bouncer at the door (client-side UX) and a notarized ID check at the desk (server-side security).
Quick summary — key takeaways
- Always validate on the server; use client-side validation for UX.
- Use HTML5 attributes and the Constraint Validation API for quick wins.
- Never concatenate user input into SQL — use parameterized queries (you saw this when studying SQL injection).
- Make errors accessible and helpful.
Final thought: Forms are where users and data meet. Treat them like fragile but essential bridges: design for clarity, validate for truth, and defend for security. Your future queries (and your database) will thank you.
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!