17 KiB
Form Data Handling Reference
Section 1: Sending and Retrieving Form Data
Overview
Once form data is validated on the client-side, it is ready to submit. This section covers what happens when a user submits a form, where the data goes, and how to handle it on the server.
Client/Server Architecture
The web uses a basic client/server architecture:
- Client (web browser) sends an HTTP request to a server.
- Server (Apache, Nginx, IIS, Tomcat) responds using the same protocol.
- HTML forms are a user-friendly way to configure HTTP requests for sending data.
On the Client Side: Defining How to Send Data
The <form> element controls how data is sent. Two critical attributes are action and method.
The action Attribute
The action attribute defines where form data gets sent. It must be a valid relative or absolute URL.
Absolute URL:
<form action="https://www.example.com">...</form>
Relative URL (same origin):
<form action="/somewhere_else">...</form>
Same page (no attributes or empty action):
<form>...</form>
Security Note: Use HTTPS (secure HTTP) to encrypt data. If a secure form posts to an insecure HTTP URL, browsers display a security warning.
The method Attribute
Two main HTTP methods transmit form data: GET and POST.
GET Method
- Used by browsers to ask the server to send back a resource.
- Data is appended to the URL as query parameters.
- Browser sends an empty body.
Example Form:
<form action="https://www.example.com/greet" method="GET">
<div>
<label for="say">What greeting do you want to say?</label>
<input name="say" id="say" value="Hi" />
</div>
<div>
<label for="to">Who do you want to say it to?</label>
<input name="to" id="to" value="Mom" />
</div>
<div>
<button>Send my greetings</button>
</div>
</form>
Result URL: https://www.example.com/greet?say=Hi&to=Mom
HTTP Request:
GET /?say=Hi&to=Mom HTTP/2.0
Host: example.com
When to use: Reading data, non-sensitive information.
POST Method
- Used to send data that the server should process.
- Data is included in the HTTP request body, not the URL.
- More secure for sensitive data (passwords, etc.).
Example Form:
<form action="https://www.example.com/greet" method="POST">
<div>
<label for="say">What greeting do you want to say?</label>
<input name="say" id="say" value="Hi" />
</div>
<div>
<label for="to">Who do you want to say it to?</label>
<input name="to" id="to" value="Mom" />
</div>
<div>
<button>Send my greetings</button>
</div>
</form>
HTTP Request:
POST / HTTP/2.0
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13
say=Hi&to=Mom
When to use: Sensitive data, large amounts of data, modifying server state.
Viewing HTTP Requests in Browser DevTools
- Open Developer Tools (F12).
- Select the "Network" tab.
- Select "All" to see all requests.
- Click the request in the "Name" tab.
- View "Request" (Firefox) or "Payload" (Chrome/Edge).
On the Server Side: Retrieving the Data
The server receives data as a string parsed into key/value pairs. Access method depends on the server platform.
Example: Raw PHP
<?php
// Access POST data
$say = htmlspecialchars($_POST["say"]);
$to = htmlspecialchars($_POST["to"]);
// Access GET data
// $say = htmlspecialchars($_GET["say"]);
echo $say, " ", $to;
?>
Output: Hi Mom
Example: Python with Flask
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def form():
return render_template('form.html')
@app.route('/hello', methods=['GET', 'POST'])
def hello():
return render_template('greeting.html',
say=request.form['say'],
to=request.form['to'])
if __name__ == "__main__":
app.run()
Other Server-Side Frameworks
| Language | Frameworks |
|---|---|
| Python | Django, Flask, web2py, py4web |
| Node.js | Express, Next.js, Nuxt, Remix |
| PHP | Laravel, Laminas, Symfony |
| Ruby | Ruby On Rails |
| Java | Spring Boot |
A Special Case: Sending Files
Files are binary data and require special handling. Three steps are needed:
The enctype Attribute
This specifies the Content-Type HTTP header.
- Default:
application/x-www-form-urlencoded - For files:
multipart/form-data
File Upload Example:
<form
method="post"
action="https://example.com/upload"
enctype="multipart/form-data">
<div>
<label for="file">Choose a file</label>
<input type="file" id="file" name="myFile" />
</div>
<div>
<button>Send the file</button>
</div>
</form>
Requirements:
- Set
methodtoPOST(file content cannot go in a URL). - Set
enctypetomultipart/form-data. - Include one or more
<input type="file">controls.
Note: Servers can limit file and request sizes to prevent abuse.
Security Considerations
Be Paranoid: Never Trust Your Users
All incoming data must be checked and sanitized:
- Escape Dangerous Characters -- Watch for executable code patterns (JavaScript, SQL commands). Use server-side escaping functions. Different contexts require different escaping.
- Limit Incoming Data -- Only accept what is necessary. Set maximum sizes for requests.
- Sandbox Uploaded Files -- Store on a different server. Serve through a different subdomain or domain. Never execute uploaded files directly.
Key Rule: Never trust client-side validation alone -- always validate on the server. Client-side validation can be bypassed; the server has no way to verify what truly happened on the client.
Quick Reference: GET vs POST
| Aspect | GET | POST |
|---|---|---|
| Data location | Visible in URL as query parameters | Hidden in request body |
| Data size | Limited by URL length | No inherent limit |
| Security | Not suitable for sensitive data | Better for sensitive/large data |
| Caching | Can be cached | Not cached |
| Use case | Reading/retrieving data | Modifying server state, sending files |
Important Notes
- Form data format: Name/value pairs joined with ampersands (
name=value&name2=value2). - URL encoding: Special characters are URL-encoded in query parameters.
- Default form target: Without
action, data submits to the current page. - Secure protocol: Always use HTTPS for sensitive data.
Section 2: Form Validation
Source: https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Forms/Form_validation
Overview
Client-side form validation helps ensure data entered matches requirements set by form controls. While important for user experience, it must always be paired with server-side validation since client-side validation is easily bypassed by malicious users.
Built-In HTML Validation Attributes
required
Specifies that a form field must be filled before submission.
<input id="choose" name="i-like" required />
- Input matches
:requiredand:invalidpseudo-classes when empty. - For radio buttons, one button in a same-named group must be checked.
minlength and maxlength
Constrains character length for text fields and textareas.
<input type="text" minlength="6" maxlength="6" />
<textarea maxlength="140"></textarea>
min, max, and step
Constrains numeric values and their increments.
<input type="number" min="1" max="10" step="1" />
<input type="date" min="2024-01-01" max="2024-12-31" />
type
Validates against specific formats (email, URL, number, date, etc.).
<input type="email" />
<input type="url" />
<input type="number" />
pattern
Validates against a regular expression.
<input
type="text"
pattern="[Bb]anana|[Cc]herry"
required
/>
Pattern Examples:
| Pattern | Matches |
|---|---|
a |
Single character 'a' |
abc |
'a' followed by 'b' followed by 'c' |
ab?c |
'ac' or 'abc' |
ab*c |
'ac', 'abc', 'abbbbbc', etc. |
a|b |
'a' or 'b' |
CSS Pseudo-Classes for Validation States
Valid States
input:valid {
border: 2px solid black;
}
input:user-valid {
/* Matches after user interaction */
}
input:in-range {
/* For inputs with min/max */
}
Invalid States
input:invalid {
border: 2px dashed red;
}
input:user-invalid {
/* Matches after user interaction */
}
input:out-of-range {
/* For inputs with min/max */
}
input:required {
/* Matches required fields */
}
Complete Built-In Validation Example
<form>
<p>Please complete all required (*) fields.</p>
<fieldset>
<legend>Do you have a driver's license? *</legend>
<input type="radio" required name="driver" id="r1" value="yes" />
<label for="r1">Yes</label>
<input type="radio" required name="driver" id="r2" value="no" />
<label for="r2">No</label>
</fieldset>
<p>
<label for="age">How old are you?</label>
<input type="number" min="12" max="120" step="1" id="age" name="age" />
</p>
<p>
<label for="fruit">What's your favorite fruit? *</label>
<input
type="text"
id="fruit"
name="fruit"
list="fruits"
required
pattern="[Bb]anana|[Cc]herry|[Aa]pple"
/>
<datalist id="fruits">
<option>Banana</option>
<option>Cherry</option>
<option>Apple</option>
</datalist>
</p>
<p>
<label for="email">Email address:</label>
<input type="email" id="email" name="email" />
</p>
<button>Submit</button>
</form>
Constraint Validation API
The Constraint Validation API provides methods and properties for custom validation logic.
Key Properties
validationMessage -- Returns localized validation error message.
validity -- Returns a ValidityState object with these properties:
| Property | Description |
|---|---|
valid |
true if element meets all constraints |
valueMissing |
true if required but empty |
typeMismatch |
true if value does not match type (e.g., email) |
patternMismatch |
true if pattern does not match |
tooLong |
true if exceeds maxlength |
tooShort |
true if below minlength |
rangeOverflow |
true if exceeds max |
rangeUnderflow |
true if below min |
customError |
true if custom error set via setCustomValidity() |
willValidate -- Boolean, true if element will be validated on form submission.
Key Methods
// Check validity without submitting
element.checkValidity() // Returns boolean
// Report validity to user
element.reportValidity() // Shows browser's error message
// Set custom error message
element.setCustomValidity("Custom error text")
// Clear custom error
element.setCustomValidity("")
JavaScript Custom Validation Examples
Basic Custom Error Message
const email = document.getElementById("mail");
email.addEventListener("input", (event) => {
if (email.validity.typeMismatch) {
email.setCustomValidity("I am expecting an email address!");
} else {
email.setCustomValidity("");
}
});
Extending Built-In Validation
const email = document.getElementById("mail");
email.addEventListener("input", (event) => {
// Reset custom validity
email.setCustomValidity("");
// Check built-in constraints first
if (!email.validity.valid) {
return;
}
// Add custom constraint
if (!email.value.endsWith("@example.com")) {
email.setCustomValidity("Please enter an email with @example.com domain");
}
});
Complex Form Validation with Custom Messages
const form = document.querySelector("form");
const email = document.getElementById("mail");
const emailError = document.querySelector("#mail + span.error");
email.addEventListener("input", (event) => {
if (email.validity.valid) {
emailError.textContent = "";
emailError.className = "error";
} else {
showError();
}
});
form.addEventListener("submit", (event) => {
if (!email.validity.valid) {
showError();
event.preventDefault();
}
});
function showError() {
if (email.validity.valueMissing) {
emailError.textContent = "You need to enter an email address.";
} else if (email.validity.typeMismatch) {
emailError.textContent = "Entered value needs to be an email address.";
} else if (email.validity.tooShort) {
emailError.textContent =
`Email should be at least ${email.minLength} characters; you entered ${email.value.length}.`;
}
emailError.className = "error active";
}
Disabling Built-In Validation with novalidate
Use novalidate on the form to disable the browser's automatic validation while retaining CSS pseudo-classes:
<form novalidate>
<input type="email" id="mail" required minlength="8" />
<span class="error" aria-live="polite"></span>
</form>
Manual Validation Without the Constraint API
For custom form controls or complete control over validation:
const form = document.querySelector("form");
const email = document.getElementById("mail");
const error = document.getElementById("error");
const emailRegExp = /^[\w.!#$%&'*+/=?^`{|}~-]+@[a-z\d-]+(?:\.[a-z\d-]+)*$/i;
const isValidEmail = () => {
return email.value.length !== 0 && emailRegExp.test(email.value);
};
const setEmailClass = (isValid) => {
email.className = isValid ? "valid" : "invalid";
};
const updateError = (isValid) => {
if (isValid) {
error.textContent = "";
error.removeAttribute("class");
} else {
error.textContent = "Please enter a valid email address.";
error.setAttribute("class", "active");
}
};
email.addEventListener("input", () => {
const validity = isValidEmail();
setEmailClass(validity);
updateError(validity);
});
form.addEventListener("submit", (event) => {
event.preventDefault();
const validity = isValidEmail();
setEmailClass(validity);
updateError(validity);
});
Styling Error Messages
/* Invalid field styling */
input:invalid {
border-color: #990000;
background-color: #ffdddd;
}
input:focus:invalid {
outline: none;
}
/* Error message container */
.error {
width: 100%;
padding: 0;
font-size: 80%;
color: white;
background-color: #990000;
border-radius: 0 0 5px 5px;
}
.error.active {
padding: 0.3em;
}
Accessibility Best Practices
- Mark required fields with an asterisk in the label:
<label for="name">Name *</label> - Use
aria-livefor dynamic error messages:<span class="error" aria-live="polite"></span> - Provide clear, helpful messages that explain what is expected and how to fix errors.
- Avoid relying on color alone to indicate errors.
Validation Summary
| Approach | Pros | Cons |
|---|---|---|
| HTML built-in | No JavaScript needed, fast | Limited customization |
| Constraint Validation API | Modern, integrates with built-in features | Requires JavaScript |
| Fully manual (JS) | Complete control over UI and logic | More code, must handle everything |
- HTML validation is faster and does not require JavaScript.
- JavaScript validation provides more customization and control.
- Always validate server-side -- client-side validation is not secure.
- Use the Constraint Validation API for modern, built-in functionality.
- Provide clear error messages and guidance to users.
- Style validation states with
:validand:invalidpseudo-classes.