Posts

Node.js Test Runner: Test Course Filters Without a Framework

Practical Web Development — Lesson 7 of 10 · Intermediate A small pure function is a useful first testing target: it takes data and returns a result without network or browser dependencies. This lesson separates course filtering from the interface and tests the behaviour using Node's built-in test runner. Use a currently supported Node.js release and save the files with the .mjs extension. Define the behaviour first An empty or whitespace-only query returns every course. Matching ignores case. A query that matches nothing returns an empty array. The function should not reorder or change the caller's input array. These rules are a contract a user can observe, rather than a list of implementation details. Create filter.mjs : export function filterCourses(courses, query) { const term = query.trim().toLowerCase(); return courses.filter(course => course.title.toLowerCase().includes(term)); } Create filter.test.mjs in the same folder: import test from 'node:test'; imp...

Fetch Timeouts and Retries: Make Read Requests Predictable

Practical Web Development — Lesson 6 of 10 · Intermediate A loading spinner needs an exit path. A network request can stall, fail before a response arrives or return an HTTP error. This tutorial adds a per-attempt timeout and at most one retry for a read-only JSON request. It deliberately does not retry writes such as enrolment payments or progress changes. Build a bounded read helper Put this function in a browser module. Call it with a public same-origin JSON endpoint. A local test server with a delayed endpoint is useful for exercising timeout behaviour. async function readJson(url, timeoutMs = 5000) { for (let attempt = 0; attempt < 2; attempt++) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); let retry = false; try { const response = await fetch(url, { signal: controller.signal }); if (!response.ok) { const error = new Error('HTTP ' + response.status); error.retry...

JavaScript Event Delegation: Handle Dynamic Course Buttons

Practical Web Development — Lesson 5 of 10 · Intermediate When a catalogue adds new rows after loading, attaching a separate click handler to each initial button can leave the new buttons inactive. Event delegation places one handler on a stable parent and identifies which descendant initiated the event. This example uses real buttons, so keyboard activation keeps its normal browser behaviour. Create the list and one parent listener <ul id="catalogue"> <li>HTML <button type="button" data-course="html"><span>Shortlist</span></button></li> </ul> <button id="add" type="button">Add CSS row</button> <p id="status" role="status"></p> <script> const catalogue = document.querySelector('#catalogue'); const selected = new Set(); catalogue.addEventListener('click', event => { if (!(event.target instanceof Element)) return; con...

localStorage Tutorial: Save a Course Shortlist Safely

Practical Web Development — Lesson 4 of 10 · Intermediate A shortlist can help a learner remember courses between visits on the same browser. For a small non-sensitive list, localStorage is convenient. It is not a database shared across devices and it should not be the authority for enrolment or paid access. This exercise stores only known course IDs and treats everything read back as untrusted data. Use a versioned key and validate the shape Create shortlist.js with the following functions. Run the example on a local HTTP page, not by depending on file-URL storage behaviour. const KEY = 'course-shortlist-v1'; const allowed = new Set(['html', 'css', 'js']); function readShortlist() { try { const parsed = JSON.parse(localStorage.getItem(KEY) || '[]'); if (!Array.isArray(parsed)) return []; return [...new Set(parsed.filter(id => typeof id === 'string' && allowed.has(id)))]; } catch { return []; } } function...

JavaScript URLSearchParams: Share a Course Filter in a Link

Practical Web Development — Lesson 3 of 10 · Intermediate A search box that resets after reload is frustrating when someone wants to share a filtered list. Store the public search term in the URL so a link can recreate the same view. This exercise uses URL , URLSearchParams and the History API without a framework. Run it from a local HTTP server so the page has a normal web origin. Build the complete small example <label for="query">Find a course</label> <input id="query" type="search"> <p id="count" role="status"></p><ul id="courses"></ul> <script> const courses = ['HTML fundamentals', 'CSS layout', 'JavaScript DOM']; const input = document.querySelector('#query'); const list = document.querySelector('#courses'); const count = document.querySelector('#count'); const initial = new URL(location.href); input.value = (initial.searchPa...

CSS Container Queries: Make Course Cards Fit Any Column

Practical Web Development — Lesson 2 of 10 · Beginner to intermediate A course card may appear in a wide catalogue and a narrow sidebar on the same screen. A viewport media query sees the same screen width in both locations. A container query lets the card respond to the space its parent actually provides. This exercise uses a named inline-size container and keeps the default layout readable without the query. Separate the measuring box from the card The wrapper is the query container. Its descendant changes layout. Keeping those responsibilities separate avoids trying to make an element query its own size. Copy this into an HTML file; the two wrappers intentionally have different maximum widths. <style> .course-slot { container: course / inline-size; max-width: 48rem; margin: 1rem 0; } .course-slot.narrow { max-width: 20rem; } .course-card { display: grid; gap: 1rem; padding: 1rem; border: 1px solid #888; border-radius: .5rem; } .course-card h2 { margin: 0; overflow-wrap: anyw...

HTML Form Validation: Build an Accessible Course Enquiry Form

Practical Web Development — Lesson 1 of 10 · Beginner A course enquiry form should tell a learner what information to enter, catch simple mistakes and explain whether anything was sent. This tutorial builds a local demonstration with native HTML controls. It does not send enquiries to a real inbox. Save the example as index.html and open it in a browser. Start with labels and useful constraints Each visible label points to one input through matching for and id values. A placeholder is not a substitute: it disappears while someone types. Required fields are identified in the label, and the optional message has a length limit. Only collect information you need to answer the question. <!doctype html> <html lang="en"><meta charset="utf-8"> <title>Course enquiry demo</title> <form id="enquiry"> <p><label for="email">Email (required)</label> <input id="email" name="email...

Web App Architecture: Design a Reliable Course Platform

Web Development Learning Series — Part 10 of 10 · Senior Senior web development involves choosing tradeoffs that a team can operate and maintain. A course platform needs more than a frontend framework: it needs clear rules about content, enrollment, identity, failures, and releases. This article turns those concerns into a concrete design exercise. Assume a modest platform with public course pages, signed-in learners, and saved progress. The team is small. Start with that situation rather than copying the architecture of a much larger company.

Playwright Tutorial: Test a Course Filter and Empty State

Web Development Learning Series — Part 9 of 10 · Advanced A useful browser test protects behavior that matters to a visitor. For a course catalog, that might be finding a beginner lesson and understanding when a filter has no results. A test that only proves a button exists gives less confidence. This guide assumes an application with a visible Difficulty label, a select control, course cards, and a status message. Use the official Playwright setup instructions for a project that does not already have the test runner installed.

Core Web Vitals: Diagnose LCP, INP and CLS

Web Development Learning Series — Part 8 of 10 · Advanced A fast website helps learners reach the lesson and use its controls without unnecessary waiting. Performance work begins by identifying the slow experience, not by applying every optimization tip at once. This guide uses a course landing page as a practical example. Imagine the page opens with a large banner image, a signup widget, and a video lesson. Those features can affect loading, interaction, and stability in different ways. Measure each problem separately before changing the design.