Node.js: Code Outside the Browser / Building a Basic Server-Side Application (Without Framework)

HTML Templating: Filling the Templates

🎯 Goal of This Note

Take the HTML templates built earlier (template-overview, template-card, template-product) and replace their placeholders with real data from the JSON file, so the pages become truly dynamic.


πŸ—‚οΈ Step 1: Organize the Code with Comments

πŸ“ Add comment sections to keep the routing logic organized in index.js

// OVERVIEW PAGE
// PRODUCT PAGE
// API
// NOT FOUND

⚑ Step 2: Load All Templates Once (Top-Level Code)

πŸ“– Just like the JSON data was read once at the top level (not inside the request callback), the same approach applies to templates β€” they never change, so there's no need to re-read them on every request.

index.js

const tempOverview = fs.readFileSync(`${__dirname}/templates/template-overview.html`, 'utf-8');
const tempCard = fs.readFileSync(`${__dirname}/templates/template-card.html`, 'utf-8');
const tempProduct = fs.readFileSync(`${__dirname}/templates/template-product.html`, 'utf-8');

πŸ€”πŸ’‘ Why Synchronous (readFileSync) Is Fine Here

  • πŸš€ This code runs only once, when the app starts (top-level code).

  • ⚠️ If it were inside the createServer callback, it would block execution on every single request β€” a serious performance problem under heavy traffic.

  • βœ… Since it's outside the callback, blocking briefly at startup is harmless.


πŸ§ͺ Step 3: Test by Sending the Raw Template First

πŸ‘€ Before adding replacement logic, first confirm the template loads correctly:

if (pathName === '/' || pathName === '/overview') {
  res.writeHead(200, { 'Content-Type': 'text/html' });
  res.end(tempOverview);
  • πŸ”„ Restart the server and reload the browser.

  • πŸ‘€ You should see the styled page, but the product cards will be missing β€” because the %PRODUCT_CARDS% placeholder hasn't been replaced yet.


πŸ› οΈ Step 4: Build a Reusable replaceTemplate Function

♻️ Since each product card needs the same set of placeholders replaced (just with different data), it makes sense to create one reusable function.

const replaceTemplate = (temp, product) => {
  let output = temp.replace(/{%PRODUCTNAME%}/g, product.productName);
  output = output.replace(/{%IMAGE%}/g, product.image);
  output = output.replace(/{%PRICE%}/g, product.price);
  output = output.replace(/{%FROM%}/g, product.from);
  output = output.replace(/{%NUTRIENTS%}/g, product.nutrients);
  output = output.replace(/{%QUANTITY%}/g, product.quantity);
  output = output.replace(/{%DESCRIPTION%}/g, product.description);
  output = output.replace(/{%ID%}/g, product.id);

  if (!product.organic)
    output = output.replace(/{%NOT_ORGANIC%}/g, 'not-organic');

  return output;
};

1. Function Parameters

  • temp β€” the template string (e.g. tempCard)

    const tempCard = fs.readFileSync(`${__dirname}/templates/template-card.html`, 'utf-8');
  • product β€” a single product object (e.g. one entry from the JSON array)

2. Why let Instead of const

  • A new variable output is created instead of modifying temp directly, since mutating function arguments is bad practice.

  • output gets reassigned each time a placeholder is replaced, so it has to be declared with let β€” const wouldn't allow that.

3. Why a Regular Expression Instead of a Plain String

temp.replace(/{%PRODUCTNAME%}/g, product.productName)
  • String.replace() with a plain string only replaces the first match.

  • A regex with the g (global) flag replaces every occurrence β€” necessary since some placeholders (like %IMAGE%) can appear more than once in the template.

4. Handling the Organic Placeholder (Special Case)

  • product.organic is a boolean (true/false) in the JSON.

  • If a product is not organic, %NOT_ORGANIC% is replaced with the CSS class not-organic, which hides the organic badge via CSS.

  • If it is organic, the placeholder is replaced with an empty string β€” no extra class gets added, so the badge stays visible.

5. Returning the Result

  • The function returns output, the final HTML string with every placeholder filled in.


πŸ” Step 5: Generate a Card for Every Product Using .map()

const cardsHtml = dataObj.map(el => replaceTemplate(tempCard, el)).join('');
console.log(cardsHtml);
  1. dataObject is the array of product objects (parsed earlier from JSON).

  2. .map() loops through each product (el) in the array and returns a new array, where each item is the result of calling replaceTemplate(tempCard, el) β€” a fully filled-in HTML card for that product.

  3. Because this is an arrow function without curly braces, the result is implicitly returned β€” no need to write return explicitly.

  4. At this point, cardsHtml is an array of HTML strings (one per product), not yet a single string.

  5. .join('') combines all array elements into one string with no separator between them, giving the final combined HTML for all the cards.

Debugging tip: it helps to console.log(cardsHtml) at intermediate steps to confirm the array/string looks right before moving on.


🧩 Step 6: Insert the Cards into the Overview Template:

Replace console.log(cardsHtml); and res.end('This is the OVERVIEW'); with this: πŸ‘‡

const output = tempOverview.replace('{%PRODUCT_CARDS%}', cardsHtml);
res.end(output);
  • πŸ”„ This replaces the single %PRODUCTCARDS% placeholder in tempOverview with the full string of all generated cards.

  • πŸ“€ Finally, send output (not the raw template) back as the response.


βœ…  Step 7: Verifying Full Dynamic Behavior

Test 1 β€” Removing a Product

  1. βœ‚οΈ Delete one product entry from data.json (e.g., cut out the avocado object).

  2. πŸš€ Restart the server β€” now reload the page, and the removed product is gone.

  3. ♻️ Add the product back, restart the server again β€” it reappears.

βœ… This confirms the page is genuinely data-driven from the JSON file.

Test 2 β€” Product Detail Links

  • πŸ”— When the cursor is placed over the details, a link will be displayed below.

  • πŸ”— Each card contains a link with a dynamic id (e.g., /product?id=0, /product?id=1, etc.), matching the product's position/ID in the JSON.

  • ❌ Clicking one of these links currently results in "Page not found", since the /product route hasn't been implemented yet to handle dynamic IDs.

πŸš€ Next step in the course: Implement the /product route so each product's detail page actually displays the correct data based on its ID.


πŸŽ―πŸ“š Key Takeaways

πŸ“Œ Concept

🎯 Purpose

πŸ“‚ Reading templates at the top level

Templates never change per-request, so read them once at startup for efficiency

πŸ”„ replaceTemplate(temp, product)

Reusable function to replace all placeholders in a template with one product's data

🌐 Regular expression with /g flag

Ensures all occurrences of a placeholder are replaced, not just the first

πŸ” let vs const for output

Use let when a variable needs to be reassigned repeatedly; avoid mutating function arguments directly

πŸƒ .map() + .join('')

Generates one HTML "card" per product, then combines them all into a single string

🐞 Debugging placeholder issues

Compare your template's HTML structure against the original reference design when something looks visually wrong

πŸš€ Data is only read at startup

Changes to data.json require a server restart to take effect (since data is loaded once into memory)