Lesson 16: Building for the Web

Capstone — putting the whole module together with form handling, validation, and backend anatomy

📅 March 2026 ⏱ ~75 min 🌐 Topic 6 of 6 — Module Capstone 📚 Year 12 Module 2
← Back to Learning Hub
🌐

Programming for the Web — Almost Done!

Lessons 13–16 cover the full NESA SE-12-05 module. This capstone lesson ties it all together — form handling, validation, backend code, and the big-picture architecture diagram you'll need for exam questions.

📋 Section 1: Form Handling ~12 min

Forms are the original input mechanism of the web. When you submit a login form, a search box, a registration page — you're triggering a form submission. Let's trace what actually happens.

HTML Form Anatomy

Every form has a few key attributes that control what happens when the user submits it.

<form action="/api/register" method="POST">
  <label for="username">Username</label>
  <input type="text" id="username" name="username" required minlength="3">

  <label for="email">Email</label>
  <input type="email" id="email" name="email" required>

  <label for="password">Password</label>
  <input type="password" id="password" name="password" required minlength="8">

  <button type="submit">Create Account</button>
</form>
Attribute What it does
action="/api/register" The URL the form data is sent to when submitted
method="POST" Sends data in the request body — not the URL. Critical for passwords.
name="username" The key in the submitted data. Without name, the field isn't included.
required HTML5 client-side validation — browser blocks submission if empty
minlength="8" HTML5 minimum character count — browser enforces before sending
type="email" Browser validates basic email format (contains @) before sending

What the Browser Sends — Raw HTTP

When you click Submit on a traditional form, the browser constructs an HTTP request that looks like this:

POST /api/register HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded

username=blake&email=blake%40example.com&password=hunter2

The form fields become URL-encoded key=value pairs in the request body, joined by &. Special characters (like @) are percent-encoded (%40).

Modern Approach: fetch() + JSON

Most modern apps don't use raw form submissions. Instead, they intercept the submit event in JavaScript, collect the values, and send JSON via fetch(). This gives more control and returns a JSON response without a full page reload.

// Instead of a raw form submission, most modern apps use fetch
const formData = {
  username: document.getElementById('username').value,
  email: document.getElementById('email').value,
  password: document.getElementById('password').value
};

const response = await fetch('/api/register', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(formData)
});
Volcanic Pantheon: Your login form uses fetch + JSON. The Vercel Edge Middleware reads event.request.json() to get the submitted credentials. The pattern above is exactly what you already built.

✅ Section 2: Client-side vs Server-side Validation ~10 min

Validation is checking that input meets requirements before processing it. There are two places validation can happen — and both are needed for different reasons.

Client-side (Browser)

Fast Feedback, Zero Trust

Happens in JavaScript before the request is sent. Provides immediate feedback — red borders, inline error messages — without a network round-trip.

Uses HTML5 attributes (required, minlength, type="email") or custom JavaScript logic.

The catch: trivially bypassed. Open DevTools, disable JS, or use curl to send a raw HTTP request — you'll skip every HTML5 attribute and every JS check.

Server-side (Server)

The Only Enforceable Layer

Happens on the server after receiving the request. The attacker cannot bypass code that runs on your machine — they have no control over the server.

Must validate everything: field presence, data types, lengths, formats, and business rules.

The tradeoff: slower (requires network round-trip) and returns HTTP error codes in the response (e.g. 400 Bad Request with error details).

Never rely on client-side validation alone

If you only validate on the client side, an attacker using curl or Postman can send any data they want — bypassing every HTML5 required attribute and every JavaScript check. Server-side validation is non-negotiable. It is the only layer the attacker cannot skip.

Server Validation in Code

Here's a minimal server-side validation function — the kind you'd call inside a Netlify Function or Express handler before touching the database:

function validateRegistration(data) {
  const errors = [];

  if (!data.username || data.username.length < 3) {
    errors.push('Username must be at least 3 characters');
  }

  if (!data.email || !data.email.includes('@')) {
    errors.push('Valid email required');
  }

  if (!data.password || data.password.length < 8) {
    errors.push('Password must be at least 8 characters');
  }

  return errors; // empty array = valid
}

Return an empty array → valid. Return a non-empty array → send a 400 response with the error list.

⚡ Challenge — Spot the Gap

A developer says: "We have validation in our React forms, so we don't need server validation." What's wrong with this reasoning?

Show answer

React validation runs in the browser. An attacker can send HTTP requests directly to the API, completely bypassing the React app entirely. Using curl, Postman, or a short script, they can submit any data they like — they will never see a single React validation message.

Server validation is the only layer the attacker cannot skip, because it runs on a machine they don't control. Client-side validation is a UX feature. Server-side validation is a security requirement.

⚡ Section 3: A Minimal Netlify Function ~15 min

Luis's tutoring site uses Netlify Functions for the student login API. This is a real, working serverless backend you can look at — and the structure is worth understanding line by line.

The Full Handler — Annotated

// netlify/functions/auth.cjs

exports.handler = async function(event, context) {
  // 1. Only allow POST
  if (event.httpMethod !== 'POST') {
    return {
      statusCode: 405,
      body: JSON.stringify({ error: 'Method not allowed' })
    };
  }

  // 2. Parse the request body
  let data;
  try {
    data = JSON.parse(event.body);
  } catch (e) {
    return {
      statusCode: 400,
      body: JSON.stringify({ error: 'Invalid JSON' })
    };
  }

  // 3. Validate input
  const { username, password } = data;
  if (!username || !password) {
    return {
      statusCode: 400,
      body: JSON.stringify({ error: 'Username and password required' })
    };
  }

  // 4. Check credentials (simplified)
  if (username === 'blake' && password === process.env.BLAKE_PASSWORD) {
    return {
      statusCode: 200,
      body: JSON.stringify({ token: generateJWT(username) })
    };
  }

  return {
    statusCode: 401,
    body: JSON.stringify({ error: 'Invalid credentials' })
  };
};
Part What it is
exports.handler The function Netlify calls when a request arrives at this endpoint
event.httpMethod The HTTP method — GET, POST, DELETE, etc.
event.body The raw request body as a string — must be JSON.parse()'d
statusCode The HTTP status code to return (200 OK, 400 Bad Request, 401 Unauthorized…)
body The response body as a string — must JSON.stringify() objects
process.env.BLAKE_PASSWORD Environment variable — credentials live here, never hardcoded in source

Connection to Express: If you compare this to Express.js (app.post('/auth', (req, res) => {...})), the concept is identical — just different syntax. event is the equivalent of req. The returned object is the equivalent of calling res.status(200).json(...). The mental model transfers directly.

🏗️ Section 4: MVC Pattern ~8 min

As applications grow, mixing database code, business logic, and HTML in one file becomes unmanageable. MVC (Model-View-Controller) is the dominant pattern for organising server-side code. It gives each concern its own home.

Model

Data Layer

Database access, data validation rules, business objects.

Example: User.findByUsername(username), Post.create({title, userId})

Knows about: database queries, data structure.

Doesn't know about: HTTP, HTML, user interface.

View

Presentation Layer

HTML templates, JSON responses — the format shown to the user or client.

Example: render('profile.html', {user}) or return JSON.stringify(user)

Knows about: display format, HTML structure.

Doesn't know about: database, business rules.

Controller

Request Handler

Receives HTTP request, calls the Model, returns the View. The coordinator.

Example: receives POST /login → calls User.authenticate() → returns JSON or redirects.

Knows about: HTTP, routing.

Coordinates between: Model and View.

Volcanic Pantheon: Vercel Edge Middleware is essentially a Controller — it receives incoming requests, checks authentication state (calling what would be the Model layer), and decides what View to serve: redirect to the login page or let the user through. You've already built MVC without knowing it had a name.

🗺️ Section 5: Full Architecture Diagram ~10 min

Here's the complete picture — every component a production web application uses, from browser to database and back. This is the diagram you need to be able to describe in HSC written answers.

Browser
User types a URL or submits a form
fetch('/api/data', { method: 'POST', body: JSON.stringify(payload) }) — outgoing HTTP request
CDN — Content Delivery Network
Vercel Edge / Cloudflare
Serves cached static files (HTML, CSS, JS, images) from the datacenter nearest to the user. Cache miss for dynamic routes → passes request to the origin server.
Load Balancer
Distributes Traffic
Spreads incoming requests across multiple server instances (horizontal scaling). Runs health checks — if a server crashes, the load balancer stops sending it traffic automatically.
Server / Serverless Function
Your Code Runs Here
Controller logic: parse request, validate input, authenticate the user. Business logic: calculate, transform data, enforce application rules. For Netlify Functions: exports.handler is called.
Cache — Redis
In-memory Result Store
Check if this query result is already stored in memory: "SELECT * FROM users WHERE id=1". Cache hit → return result in microseconds. Cache miss → continue to database, then store result for next time.
Database — PostgreSQL
Persistent Storage
Execute SQL query against tables on disk. Return matching rows. Slowest layer in the chain — this is why the cache layer exists.

↑ Response flows back up the chain through each layer ↑
Browser — Response Received
Update the DOM
const data = await response.json() — parse the response, update the page without reload.

Each Component — Why It Exists

Component Problem it solves Example
CDN Latency — users far from the origin server wait longer Vercel Edge Network, Cloudflare
Load Balancer Single server can't handle millions of concurrent requests AWS ALB, Nginx upstream
Cache (Redis) Database queries are slow — identical queries shouldn't hit disk every time Redis, Memcached
Database Data must persist beyond a single request or server restart PostgreSQL, MySQL, MongoDB
Volcanic Pantheon: Your site skips most of this — static files go straight from Vercel's Edge CDN to the browser. There's no load balancer, no separate cache layer, no database. That's perfectly appropriate for a portfolio site. Understanding the full stack is what the HSC tests — not because you'll build all of it, but because you need to know why each piece exists.

📝 Section 6: HSC Exam Prep ~8 min

The HSC SE exam often asks you to explain web concepts in written answers worth 4–6 marks. Here's how the question types work, then three practice questions to try.

Question Type Patterns

Question Format Common topics Answer strategy
"Describe the purpose of X" HTTP methods, status codes, DNS, TLS, REST APIs, SQL Define → explain what it does → give one example
"Explain the steps involved in..." HTTP request/response cycle, DNS resolution, form submission Numbered list, one sentence per step, include component names
"Compare and contrast X and Y" Client vs server validation, static vs dynamic sites, GET vs POST Table or paragraph — similarities, differences, tradeoffs
"Evaluate the security of..." Given code or architecture — identify vulnerabilities, justify fixes Name the vulnerability → explain attack vector → state the fix

Practice Questions

Q1 — 4 marks

Explain the role of DNS in accessing a website, including the steps involved in a DNS lookup.

Show model answer

DNS (Domain Name System) translates human-readable domain names (such as volcanic-pantheon.vercel.app) into IP addresses that computers use to route network traffic. Without DNS, users would need to remember numerical IP addresses for every site.

Steps in a DNS lookup:

  • The browser checks its local DNS cache for a recent lookup of this domain.
  • If not found, the OS checks its hosts file and system cache.
  • The request goes to a recursive resolver (typically provided by the ISP, or a public service like 8.8.8.8).
  • The resolver queries a root name server, which directs it to the relevant TLD (top-level domain) name server (e.g. .app).
  • The TLD server directs the resolver to the authoritative name server for the domain.
  • The authoritative server returns the IP address for the domain.
  • The resolver returns the IP to the browser and caches it for the TTL (time-to-live) duration.

Q2 — 5 marks

A web developer builds a registration form with client-side validation using JavaScript. Explain why this is insufficient and describe what additional measures are required.

Show model answer

Client-side validation runs in the user's browser and can be completely bypassed — an attacker can disable JavaScript, modify the code in DevTools, or send a raw HTTP request using tools like curl or Postman, entirely skipping the HTML form and any JavaScript checks. This means any data, regardless of format or content, can be submitted directly to the server API.

Server-side validation is required because it runs on the server where the attacker has no control. The server must:

  • Check all fields are present and non-empty.
  • Verify data types and formats (e.g. valid email format, numeric fields contain numbers).
  • Enforce length limits and business rules (e.g. username uniqueness, password complexity).
  • Return meaningful HTTP error codes (400 Bad Request) with error details if validation fails.

Server-side validation also forms the defence against injection attacks (such as SQL injection) when combined with parameterised queries. Client-side validation should still be included for user experience, but it cannot be treated as a security control.


Q3 — 4 marks

Explain the difference between a static website and a dynamic website, giving an example of each.

Show model answer

A static website delivers pre-built files (HTML, CSS, JavaScript) directly to the browser — the server has no logic and simply returns files as stored on disk. Every visitor receives the same content. Example: a personal portfolio site hosted on Vercel or GitHub Pages.

A dynamic website generates content on the server in response to each request, typically by querying a database and constructing HTML (or JSON) from the result. Different users see different content based on their account, preferences, or real-time data. Example: a social media feed, an e-commerce site showing personalised recommendations, or a news site with a database of articles.

Dynamic sites require server-side languages (Node.js, Python, PHP) and typically a database, making them more complex and costly to host than static sites. Static sites are faster, cheaper, and more secure by default — but cannot serve personalised or real-time content without additional API calls.

🏁 Section 7: Module Wrap-up — What You Can Now Explain ~5 min

Six topics across four lessons. Here's what you can now explain that you couldn't before Lesson 12 — and how it all maps together.

Topics 1–2 · Lessons 13–13

The Internet & HTTP

DNS resolution chain, TCP handshake, raw HTTP request anatomy, status code families (2xx success, 3xx redirect, 4xx client error, 5xx server error), TLS certificate chain.

You can open a network tab and know exactly what every request is doing.

Topic 3 · Lesson 14

Client-Server Architecture

The trust boundary (browser = untrusted, server = trusted), why client-side auth fails, the spectrum from static → dynamic → serverless → edge and when to choose each.

You can explain why Vercel Edge Middleware was the right fix for Volcanic Pantheon.

Topic 4 · Lesson 14

APIs & JSON

REST conventions, idempotency, all HTTP methods (GET, POST, PUT, PATCH, DELETE), API key vs Bearer token vs OAuth — when to use each and why.

You can read any API's documentation and orient yourself immediately.

Topic 5 · Lesson 15

Databases on the Web

Relational tables and relationships, SQL CRUD operations, the full request → DB → response chain, the exact point SQL injection enters the picture.

You can design a simple database schema and write basic queries.

Topic 6 · Lesson 16 (this lesson)

Building for the Web

Form handling end-to-end, why server-side validation is mandatory (and client-side is just UX), Netlify Function anatomy, MVC pattern, and the full production architecture diagram.

You can read a backend codebase and orient yourself.

Cross-module · Lessons 7–11 + 13–16

Security (Modules 1 + 2 combined)

Every attack from the Secure Software Architecture module now has a structural home in the architecture you learned here. XSS lives in the View. SQL injection lives in the Model. CSRF lives in the Controller. Hardcoded secrets live in .env (or shouldn't).

The two modules form one complete mental model.

Programming for the Web — Complete ✓

That's the full NESA SE-12-05 module done. Two Year 12 modules complete. Next up: Software Automation.

← Back to Learning Hub