Full Stack Application Development

Chapter 5
Client-Side Programming

Certificate in Full stack programming
Full Stack Application Development   •   Certificate

Client-Side Programming

Introduction and Context

Client-side programming is the development of software that runs in a user’s web browser. In a full-stack application, the client communicates with the server through HTTP requests, presents data to users, collects input, and responds immediately to interaction. HTML defines structure, CSS controls presentation, and JavaScript supplies behaviour and application logic.

Modern client-side development is not limited to visual effects. JavaScript applications validate data, manage application state, call APIs, handle asynchronous operations, store information locally, and update the Document Object Model (DOM) without requiring a complete page reload. These capabilities support responsive interfaces such as shopping carts, dashboards, booking systems, and online forms.

This chapter uses standard browser JavaScript and emphasizes principles that remain applicable when frameworks such as React, Vue, or Angular are introduced.

Learning Objectives

By the end of this chapter, a learner should be able to:

  • explain JavaScript’s role in a full-stack application;
  • use variables, data types, operators, functions, arrays, and objects;
  • organize code with modules and understand scope;
  • respond to browser events and manipulate the DOM safely;
  • validate user input on the client while recognizing the need for server validation;
  • use asynchronous programming with Promises and async/await;
  • handle errors and communicate useful feedback to users;
  • use browser storage appropriately;
  • design a small interactive client-side feature using sound professional practices.

1. The Client-Side Runtime

A browser provides a JavaScript runtime and a set of Web APIs. JavaScript is the programming language; the DOM, Fetch API, Web Storage API, timers, and browser events are APIs supplied by the environment. This distinction is important: JavaScript itself does not define document, fetch, or localStorage; the browser does.

The browser parses an HTML document into a DOM tree. JavaScript can locate nodes in that tree, read their properties, change their content or attributes, and create new nodes. The browser then renders the updated document.

A simplified interaction cycle is:

  1. The browser loads HTML, CSS, and JavaScript.
  2. JavaScript registers event listeners and initializes state.
  3. A user performs an action, such as clicking a button.
  4. The browser dispatches an event.
  5. A handler reads the event and application state.
  6. The handler updates the DOM or requests data from a server.
  7. The browser presents the new state.

Client-side code should be regarded as untrusted code running on the user’s device. It improves usability, but it cannot enforce security rules by itself.

2. JavaScript Fundamentals

Variables, values, and types

Use const when a variable binding will not be reassigned and let when reassignment is required. Avoid var in modern code because its function-scoped behaviour can cause confusing results.

const productName = "Notebook";
let quantity = 2;
quantity += 1;

Common JavaScript types include:

TypeExampleTypical use
String"Ada"Text
Number19.95Integer and floating-point values
BigInt9007199254740993nVery large integers
BooleantrueLogical state
UndefinedundefinedMissing assigned value
NullnullIntentional absence of a value
Object{ id: 4 }Structured data
Array["red", "blue"]Ordered collections
SymbolSymbol("id")Unique identifiers

JavaScript is dynamically typed, so a variable can receive values of different types. This flexibility requires careful validation. Strict equality, ===, is generally preferred to loose equality, ==, because it does not perform implicit type conversion.

console.log(5 === "5"); // false
console.log(5 == "5");  // true, because conversion occurs

Operators and expressions

Arithmetic operators include +, -, *, /, and %. Comparison operators include >, <, >=, <=, ===, and !==. Logical operators are &&, ||, and !.

The + operator concatenates strings as well as adding numbers. Therefore, values obtained from form controls should be converted deliberately:

const quantity = Number(quantityInput.value);
if (!Number.isInteger(quantity) || quantity < 1) {
  throw new Error("Quantity must be a positive whole number");
}

Functions

Functions package reusable behaviour. Parameters receive input and return supplies a result.

function calculateSubtotal(price, quantity) {
  return price * quantity;
}

const subtotal = calculateSubtotal(12.50, 3);

Arrow functions provide a concise syntax:

const double = number => number * 2;

A function should ideally have one clear responsibility. Small functions are easier to test, reuse, and debug.

Arrays and objects

Arrays support methods such as map, filter, find, and reduce.

const prices = [10, 20, 30];
const discounted = prices.map(price => price * 0.9);
const expensive = prices.filter(price => price > 15);

Objects represent related properties:

const customer = {
  id: 17,
  name: "Amina",
  active: true
};

Destructuring extracts values clearly:

const { name, active } = customer;

A shallow copy can be made with spread syntax:

const updatedCustomer = { ...customer, active: false };

Spread syntax does not deeply clone nested objects. Applications that modify nested data must account for that limitation.

3. Scope, Modules, and Code Organization

Scope determines where a name can be accessed. Variables declared with let and const are block-scoped. A name declared inside a function is normally unavailable outside that function. Restricting scope reduces accidental coupling.

A module is a JavaScript file with explicit imports and exports. Modules make dependencies visible and help divide a system into coherent units.

// cart.js
export function total(items) {
  return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// app.js
import { total } from "./cart.js";

const amount = total([
  { price: 10, quantity: 2 },
  { price: 5, quantity: 1 }
]);

The HTML document must load the entry module:

<script type="module" src="/js/app.js"></script>

Modules are deferred by default: they are fetched and executed after document parsing, while preserving dependency order. Production applications commonly use a build tool to bundle, optimize, and test modules, but the underlying import/export model remains the same.

4. Events and DOM Manipulation

An event is a notification that something has happened, such as a click, key press, form submission, or change to an input. The preferred approach is addEventListener rather than inline HTML event attributes.

const button = document.querySelector("#saveButton");

button.addEventListener("click", event => {
  event.preventDefault();
  console.log("Save requested");
});

The event object provides information about the event and its target. For forms, listening to submit is preferable to listening only to a button click because it supports keyboard submission and other valid submission methods.

Selecting and changing elements

Useful DOM methods include:

  • querySelector(selector), which returns the first matching element;
  • querySelectorAll(selector), which returns a collection of matches;
  • textContent, which safely sets or reads text;
  • classList.add, remove, and toggle, which manage CSS classes;
  • setAttribute, which changes an attribute;
  • createElement, which creates a new element.

Use textContent for ordinary user-provided text. Assigning untrusted content to innerHTML can create cross-site scripting vulnerabilities if the content contains executable markup.

const message = document.querySelector("#message");
message.textContent = "Order saved";
message.classList.add("success");

Event propagation and delegation

Events commonly propagate from the target upward through ancestor elements. Event delegation attaches one listener to a stable parent and examines event.target. This is useful when list items are created dynamically.

const list = document.querySelector("#tasks");

list.addEventListener("click", event => {
  const deleteButton = event.target.closest("[data-delete]");
  if (!deleteButton) return;
  deleteButton.closest("li").remove();
});

Delegation reduces the number of listeners, but the handler must verify that the target is the intended control.

5. Forms and Validation

Validation checks whether input is complete, correctly formatted, and acceptable for an operation. HTML provides basic constraints such as required, type="email", min, max, and pattern. JavaScript can provide immediate, context-specific feedback.

<form id="registrationForm">
  <label>
    Email
    <input id="email" name="email" type="email" required>
  </label>
  <label>
    Password
    <input id="password" name="password" type="password" minlength="8" required>
  </label>
  <button type="submit">Register</button>
  <p id="formError" role="alert"></p>
</form>
const form = document.querySelector("#registrationForm");
const formError = document.querySelector("#formError");

form.addEventListener("submit", event => {
  event.preventDefault();
  formError.textContent = "";

  const data = new FormData(form);
  const email = String(data.get("email")).trim();
  const password = String(data.get("password"));

  if (!email || password.length < 8) {
    formError.textContent = "Enter a valid email and a password of at least eight characters.";
    return;
  }

  // Send data only after client-side checks succeed.
});

Client-side validation is a usability feature, not a security boundary. A user can disable JavaScript or send a request directly to an API. The server must repeat validation, authorize the operation, protect against injection, and apply business rules. Error messages should be specific enough to help users but should not disclose sensitive system information.

6. Asynchronous Programming

Network operations and timers take time. Blocking the main thread would make the interface unresponsive, so browsers use asynchronous APIs. A Promise represents the eventual success or failure of an operation.

fetch("/api/products")
  .then(response => {
    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }
    return response.json();
  })
  .then(products => console.log(products))
  .catch(error => console.error(error));

fetch rejects its Promise for network-level failures, but normally does not reject merely because the server returns an HTTP error such as 404 or 500. Consequently, code should inspect response.ok or response.status.

The async/await syntax expresses the same process more readably:

async function loadProducts() {
  const response = await fetch("/api/products");
  if (!response.ok) {
    throw new Error(`HTTP error: ${response.status}`);
  }
  return response.json();
}

async function initialize() {
  try {
    const products = await loadProducts();
    renderProducts(products);
  } catch (error) {
    showError("Products could not be loaded. Please try again.");
    console.error(error);
  }
}

await pauses the current asynchronous function, not the entire browser. Independent operations can run concurrently:

const [profileResponse, ordersResponse] = await Promise.all([
  fetch("/api/profile"),
  fetch("/api/orders")
]);

Promise.all fails when any input Promise rejects. When results should be collected regardless of individual failures, Promise.allSettled may be more suitable.

7. Error Handling and Application State

Errors may arise from invalid input, unavailable networks, expired sessions, malformed responses, or programming defects. Good error handling has three purposes: preserve application stability, inform the user appropriately, and provide diagnostic information to developers.

Use try/catch around operations that may fail:

try {
  const settings = JSON.parse(rawSettings);
  applySettings(settings);
} catch (error) {
  console.error("Invalid settings data", error);
  useDefaultSettings();
}

Do not silently catch errors. At the same time, do not expose stack traces, tokens, or internal system details in the interface. A useful interface distinguishes loading, success, empty, and error states.

State is the current information that determines what the interface displays. A simple application might have:

const state = {
  products: [],
  loading: false,
  error: null,
  filter: ""
};

A predictable design updates state through deliberate functions and then renders from state. This reduces inconsistencies in which one part of the screen says “saved” while another shows old data.

8. Browser Storage

The Web Storage API provides localStorage and sessionStorage. Both store string key-value pairs. localStorage persists after the browser is closed; sessionStorage normally lasts for the page session and is separated by browser tab.

const cart = [{ id: 1, quantity: 2 }];
localStorage.setItem("cart", JSON.stringify(cart));

const saved = localStorage.getItem("cart");
const restoredCart = saved ? JSON.parse(saved) : [];

JSON conversion is required because storage values are strings. Parsing can fail if data is corrupted, so production code should use error handling. Storage capacity is limited and synchronous; it is not appropriate for large datasets or high-frequency writes. IndexedDB is more suitable for substantial structured client-side data.

Never store passwords, private keys, or sensitive authentication material in Web Storage. A cross-site scripting vulnerability could allow malicious code to read it. Applications should also consider consent, retention, user deletion, and privacy requirements. Cookies differ from Web Storage because cookies are automatically sent with matching HTTP requests and can be configured with security attributes such as HttpOnly, Secure, and SameSite.

9. Integrated Applied Case: Illustrative Shopping Cart

Background

An online stationery shop wants a cart that allows users to add products, change quantities, see a subtotal, and retain the cart during a browser session.

Problem

The interface must react immediately to button clicks, reject invalid quantities, preserve state, and obtain product information from an API. It must also remain usable when the network fails.

Analysis

The client needs four responsibilities: data retrieval, state management, rendering, and event handling. The cart is application state; the DOM is a representation of that state. Separating these responsibilities makes the feature easier to maintain.

Approach and solution

const state = {
  products: [],
  cart: JSON.parse(localStorage.getItem("cart") || "{}"),
  loading: false,
  error: null
};

function cartTotal() {
  return Object.entries(state.cart).reduce((total, [id, quantity]) => {
    const product = state.products.find(item => item.id === Number(id));
    return product ? total + product.price * quantity : total;
  }, 0);
}

function saveCart() {
  localStorage.setItem("cart", JSON.stringify(state.cart));
}

function addToCart(id) {
  state.cart[id] = (state.cart[id] || 0) + 1;
  saveCart();
  renderCart();
}

function renderCart() {
  const output = document.querySelector("#cartTotal");
  output.textContent = `Total: $${cartTotal().toFixed(2)}`;
}

async function loadProducts() {
  state.loading = true;
  try {
    const response = await fetch("/api/products");
    if (!response.ok) throw new Error("Product request failed");
    state.products = await response.json();
    renderCart();
  } catch (error) {
    state.error = "Products are temporarily unavailable.";
    document.querySelector("#error").textContent = state.error;
    console.error(error);
  } finally {
    state.loading = false;
  }
}

The example uses a numeric calculation for a display subtotal. For real financial systems, floating-point arithmetic can produce representation errors; prices should generally be represented as integer minor units, such as cents, or calculated using a server-side decimal strategy. The server must recalculate the final amount because client data can be changed.

Outcome

The cart responds without full-page reloads, retains non-sensitive state, displays a user-friendly failure message, and separates data operations from rendering. A production implementation would add authentication controls, server-side price verification, accessibility testing, automated tests, loading indicators, and protection against duplicate requests.

Learning points

The case demonstrates that client-side programming is a coordinated process rather than a collection of isolated syntax features. Events initiate actions, state records facts, asynchronous functions obtain data, validation protects usability, rendering updates the DOM, and storage supports continuity.

10. Best Practices, Limitations, and Contemporary Practice

  • Prefer semantic HTML and accessible labels before adding JavaScript.
  • Use modules and small functions with clear responsibilities.
  • Treat all external data as untrusted; validate and encode it appropriately.
  • Use textContent rather than unsafe HTML insertion for ordinary text.
  • Provide keyboard support, visible focus, meaningful labels, and accessible error messages.
  • Keep API keys and authorization decisions out of client-only code.
  • Abort obsolete requests where appropriate, especially during rapid searching.
  • Test normal, empty, slow-network, offline, and failure conditions.
  • Use browser developer tools to inspect the DOM, network requests, storage, and console errors.
  • Consider performance: avoid unnecessary DOM work, large synchronous computations, and excessive storage writes.

Frameworks and TypeScript can improve large applications through component models and static checking, but they do not remove the need to understand DOM events, asynchronous behaviour, HTTP status handling, security, and browser constraints. AI coding assistants can generate useful examples, yet generated code must be reviewed for incorrect assumptions, insecure DOM insertion, exposed secrets, accessibility problems, and inadequate error handling.

Chapter Summary

Client-side programming enables a browser-based application to respond to users, display data, and communicate with a server. JavaScript provides variables, functions, objects, arrays, modules, and control flow, while browser APIs provide the DOM, events, Fetch, and storage. Event listeners connect user actions to application logic; DOM methods update the interface; validation improves input quality but does not replace server-side security. Promises and async/await support non-blocking network operations, while structured error handling gives users useful feedback and developers diagnostic information. Web Storage can preserve small, non-sensitive values, but it has security, capacity, and privacy limitations. Reliable client-side applications separate state, rendering, data access, and event handling, and they are designed for accessibility, failure, security, and maintainability.

Key Terms

JavaScript: A programming language widely used to add behaviour to web pages.

Browser runtime: The environment that executes JavaScript and exposes Web APIs.

DOM: The browser’s object representation of an HTML document.

Event: A notification of an action or occurrence in the browser.

Event listener: A function registered to respond to an event.

Event delegation: Handling events through a common ancestor element.

Module: A JavaScript file with explicit imports and exports.

Scope: The region in which a variable or name can be accessed.

Promise: An object representing the eventual result of an asynchronous operation.

async/await: Syntax for writing Promise-based asynchronous code in a sequential style.

Fetch API: A browser API for making HTTP requests.

DOM manipulation: Reading or changing document elements through JavaScript.

Form validation: Checking whether submitted input meets required conditions.

State: Data that determines the current condition of an application or interface.

Web Storage: Browser storage provided through localStorage and sessionStorage.

JSON: A text-based format commonly used to exchange structured data.

XSS: Cross-site scripting, in which injected content causes unintended script execution.

API: An interface through which software components communicate.

Client-side validation: Validation performed in the browser before a request is sent.

Server-side validation: Validation performed by the trusted server before processing data.

Review Questions

Short Answer Questions

  1. What is the difference between JavaScript and a browser Web API?
  2. Why is strict equality generally preferred to loose equality?
  3. What is the purpose of addEventListener?
  4. Why must client-side validation be repeated on the server?
  5. What is the difference between localStorage and sessionStorage?

Long Answer Questions

  1. Explain how an event travels from a user action to a visible DOM update.
  2. Describe JavaScript modules and explain how imports and exports improve maintainability.
  3. Explain Promises and compare .then() with async/await.
  4. Discuss the security and privacy limitations of Web Storage.
  5. Explain how state, rendering, event handling, and data access should be separated in an interactive application.

Analytical / Application Questions

  1. Design a client-side registration form that validates required fields, displays accessible errors, and sends data only after successful checks. Identify which checks must also be implemented on the server.
  2. A product search sends a request for every keystroke, and responses sometimes arrive out of order. Propose a strategy using debouncing, request cancellation, or response identification to ensure that stale results are not displayed.
  3. A shopping cart stores prices as floating-point numbers in localStorage. Analyze the risks and propose a more reliable representation for displaying and calculating monetary values.

Multiple Choice Questions with Answers

Question 1

Which declaration is most appropriate when a variable binding should not be reassigned?

A. var

B. let

C. const

D. static

Correct Answer: C. const

Question 2

Which property is generally safest for inserting ordinary user-provided text into an element?

A. innerHTML

B. textContent

C. outerHTML

D. document.write

Correct Answer: B. textContent

Question 3

What does fetch normally do when a server responds with HTTP 404?

A. It always rejects automatically

B. It returns a response whose ok property is false

C. It converts the response to JSON automatically

D. It retries indefinitely

Correct Answer: B. It returns a response whose ok property is false

Question 4

Which event is usually preferred for handling form submission?

A. mouseover

B. change on the submit button only

C. submit on the form

D. load on the document

Correct Answer: C. submit on the form

Question 5

What type of value does localStorage store directly?

A. Only functions

B. String key-value pairs

C. Binary database tables

D. DOM trees

Correct Answer: B. String key-value pairs

Question 6

Why should client-side validation not be treated as a security boundary?

A. Browsers cannot display validation messages

B. Users can bypass or alter browser-side code and send requests directly

C. HTML forms cannot contain text

D. Validation always requires a database

Correct Answer: B. Users can bypass or alter browser-side code and send requests directly

Question 7

What does Promise.all do when one of its input Promises rejects?

A. It waits forever

B. It resolves with only successful values

C. It rejects with the failure

D. It converts the failure into null

Correct Answer: C. It rejects with the failure

Question 8

Which technique is useful for handling clicks on dynamically created list items with one parent listener?

A. Event delegation

B. 深 copying

C. Polling only

D. Inline CSS

Correct Answer: A. Event delegation

Question 9

Which storage choice is generally unsuitable for passwords or private keys?

A. localStorage

B. A server-controlled secret store

C. A protected server database

D. An operating-system credential manager

Correct Answer: A. localStorage

Question 10

Why should a server recalculate a shopping cart’s final price?

A. The browser cannot display currency

B. Client-side values can be modified by the user

C. JavaScript cannot add numbers

D. HTTP cannot carry prices

Correct Answer: B. Client-side values can be modified by the user

Full Stack Application Development
Chapter 5 of 14