Forms, Validation & Fetch API

Handle form submission in JavaScript, implement client-side validation, and communicate with APIs using the Fetch API and async/await.

Medium 65m 3 tasks
Prerequisites: DOM Manipulation & Events

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.

✦ Answer the questions to complete this task

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.

✦ Answer the questions to complete this task

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.

✦ Answer the questions to complete this task

What HTTP header carries the CSRF token in Django AJAX requests?

💪 Exercises & Challenges

📝 MCQ Medium +20 XP

Forms, Validation & Fetch MCQ

Forms, Validation & Fetch MCQ

Start →
⚙️ Practical Medium +30 XP

Scan API Client

Scan API Client

Start →
🚩 Challenge Hard +50 XP

CSRF Token Bypass

CSRF Token Bypass

Start →