Forms, Validation & Fetch API
Handle form submission in JavaScript, implement client-side validation, and communicate with APIs using the Fetch API and async/await.
Learning Objectives
- → Intercept form submissions and read field values
- → Implement client and server-side validation patterns
- → Use the Fetch API to make GET and POST requests
- → Handle JSON API responses and errors
- → Show loading states and error messages in the UI
Intercepting Form Submission
const form = document.querySelector("#scan-form");
form.addEventListener("submit", async (e) => {
e.preventDefault(); // stop normal POST/redirect
const host = form.querySelector("[name='host']").value.trim();
const port = parseInt(form.querySelector("[name='port']").value);
if (!validateForm(host, port)) return;
await submitScan(host, port);
});
Reading Form Values
// By name attribute
const host = form.elements["host"].value;
// FormData — serializes the whole form
const fd = new FormData(form);
const host = fd.get("host");
const file = fd.get("upload"); // works for file inputs too
// Convert to plain object
const data = Object.fromEntries(fd.entries());
console.log(data); // { host: "10.0.0.1", port: "80" }
Client-Side Validation
function validateForm(host, port) {
clearErrors();
// IP validation
const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/;
if (!ipRegex.test(host)) {
showError("host", "Enter a valid IPv4 address");
return false;
}
const octets = host.split(".").map(Number);
if (octets.some(o => o < 0 || o > 255)) {
showError("host", "Each octet must be 0-255");
return false;
}
// Port validation
if (!Number.isInteger(port) || port < 1 || port > 65535) {
showError("port", "Port must be 1-65535");
return false;
}
return true;
}
function showError(field, message) {
const el = document.querySelector(`#${field}-error`);
if (el) {
el.textContent = message;
el.style.display = "block";
}
}
function clearErrors() {
document.querySelectorAll(".error").forEach(e => e.textContent = "");
}
The Fetch API
// GET
const res = await fetch("https://httpbin.org/get");
const data = await res.json();
// POST with JSON body
const res = await fetch("/api/scan", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ host: "10.0.0.1", port: 80 }),
});
// POST with FormData (multipart, for file uploads)
const fd = new FormData(form);
const res = await fetch("/api/upload", {
method: "POST",
body: fd, // no Content-Type header — browser sets it with boundary
});
Full Error Handling Pattern
async function scanHost(host, port) {
const statusEl = document.querySelector("#status");
statusEl.textContent = "Scanning...";
statusEl.className = "loading";
try {
const res = await fetch("/api/scan", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ host, port }),
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.message || `HTTP ${res.status}`);
}
const data = await res.json();
renderResults(data);
statusEl.textContent = "Scan complete";
statusEl.className = "success";
} catch (err) {
statusEl.textContent = `Error: ${err.message}`;
statusEl.className = "error";
console.error(err);
}
}
CSRF Protection
Cross-Site Request Forgery: attacker tricks user's browser into making requests.
<!-- Django CSRF token in form -->
<form method="POST">
{% csrf_token %}
...
</form>
// Include CSRF token in Fetch requests
const csrfToken = document.querySelector("[name=csrfmiddlewaretoken]").value;
await fetch("/api/scan", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": csrfToken,
},
body: JSON.stringify({ host }),
});
Mini Project – Scan Form with Live Feedback
document.addEventListener("DOMContentLoaded", () => {
const form = document.querySelector("#scan-form");
const spinner = document.querySelector("#spinner");
const results = document.querySelector("#results");
form.addEventListener("submit", async (e) => {
e.preventDefault();
const host = new FormData(form).get("host").trim();
if (!host) {
showError("host", "Host is required");
return;
}
spinner.style.display = "block";
results.innerHTML = "";
try {
const res = await fetch(`https://httpbin.org/anything`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ host }),
});
const data = await res.json();
results.textContent = JSON.stringify(data, null, 2);
} catch (err) {
results.textContent = "Error: " + err.message;
} finally {
spinner.style.display = "none";
}
});
});
Write a validateForm() function that checks: IP format (regex), each octet 0-255, port 1-65535. Show inline error messages in elements next to each field. Return false on any error.
What FormData method reads the value of a named field?
What should you call first in form submit handler to prevent page reload?
Write an async function lookupHost(hostname) that fetches https://httpbin.org/get?host={hostname} and returns the JSON. Call it from a form submit handler and display the result in a
element.
What does res.ok check?
Extend your scan form to POST to a Django endpoint. Include the CSRF token from document.querySelector('[name=csrfmiddlewaretoken]').value in the X-CSRFToken header. Handle 400 errors by showing the error message from the JSON response.
What HTTP header carries the CSRF token in Django AJAX requests?