Server Side Rendering met EJS
 

Server Side Rendering met EJS

HERHALING

nodemon

Installeer nodemon als lokale development dependency

npm install nodemon --save-dev

Eventuele configuratie kan in nodemon.json

{
  "watch": ["./src"],
  "ext": "js, css, html, json"
}

Of als global dependency (= voor jouw computer)

npm install -g nodemon

nodemon

En gebruik dan steeds nodemon (ipv node) om je
Node-applicatie te starten

// package.json
{
  // ...
  "scripts": {
    // ...
    "start": "nodemon index.js"
  }
}

nodemon

Je kunt de configuratiebestand nodemon.json optioneel meegeven in het commando, maar dit is alleen nodig als de configuratie een andere bestandsnaam of locatie heeft.

// package.json
{
  // ...
  "scripts": {
    // ...
    "start": "nodemon --config nodemon.json ./src/app.js"
  }
}

Omgevingsvariabelen

Node v22.x biedt CLI support voor omgevingsvariabelen. De optie --env-file laadt omgevingsvariabelen uit een bestand (relatief aan de huidige directory) en maakt ze beschikbaar aan applicaties via process.env.

node --env-file=.env ./src/app.js 

Om na te gaan welke versie van Node bij jou actief is:

node -v

Omgevingsvariabelen

// package.json
{
  // ...
  "scripts": {
    // ...
    "start": "nodemon --env-file=.env ./src/app.js"
  }
}

Het start script in package.json zou er dus zou kunnen uitzien

Omgevingsvariabelen

Omgevingsvariabelen definieer je vervolgens in een
.env file

// .env
PORT=3000

De library dotenv heb je niet meer nodig sinds node v20.6.+

Omgevingsvariabelen

Plaats .env altijd in
.gitignore

# General
.cache
package-lock.json

# OS specific
.DS_Store

# IDE
.vscode
.idea

# Node
node_modules

.env

.env-example

  • .env-example toont welke omgevingsvariabelen nodig zijn.
  • Het bevat geen gevoelige informatie.
  • Helpt ontwikkelaars om snel hun eigen .env bestand in te stellen.
  • Zorgt voor een consistente configuratie in verschillende omgevingen.
  • Deze zit niet in .gitignore en komt dus wel in de git-repo.

Server Side Rendering met EJS

INTRODUCTIE

EJS (Embedded JavaScript Templates) is a lightweight templating engine for Node.js that enables you to embed JavaScript directly into HTML.


It uses simple <% %> syntax to inject variables, loops, and conditionals, making it easy to generate dynamic web pages efficiently.

Installatie

Installeer EJS

npm install ejs

Voorbereiding

  • Maak een bestand home.ejs aan in je src/views folder
  • Typ in home.ejs de tekst Welcome to EJS!
<!DOCTYPE html>
<html lang="nl">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="/assets/fontawesome/css/all.min.css">
  <link rel="stylesheet" href="/css/main.css">
  <title>Mijn eerste EJS applicatie</title>
</head>
<body>
  <h1>Welcome to EJS!</h1>
</body>
</html>

EJS- Express

  • Koppel nu EJS aan je gemaakte Express applicatie.
import express from "express";
// imports...

// ...

app.set('view engine', 'ejs');
app.set("views", "<pad-naar-views-folder>"); //

EJS - Express

  • Maak een nieuwe controller:
    PageController.js in de src/controllers folder
  • Plaats hier een heel eenvoudige functie die een antwoord stuurt naar de client met de render() functie.
export const home = (req, res) => {
  res.render('home');
};

EJS - Express

  • Importeer de home functie en registreer de controller in Express op de GET - "/" route 
app.get('/', home);

EJS - Express

  • Wanneer je de server herstart en via je browser de root-route opvraagt, krijg je je tekst te zien.


    Welcome to EJS!
    // EJS is up & running!

Server Side Rendering met EJS

EXPRESSIONS / TAGS

Basis tags

  • EJS tags laten je toe om aan te duiden dat de code die volgt EJS is en dus geen HTML. Om de waarde van een variabele weer te geven, gebruik je <%=
<h2><%= user.name %></h2>

Basis tags

  • Met <% kan je bepaalde control flow (loops, conditionals ...) elementen nabootsen, net zoals je dat in Javascript zou doen.
<% if (user) { %>
  <h2><%= user.name %></h2>
<% } %>
  • EJS tags afsluiten doe je steeds met %>

Geneste Objecten

{
  person: {
    firstname: "Yehuda",
    lastname: "Katz",
  },
}
<%= person.firstname %> <%= person.lastname %>

Control flow

  • Met forEach kunnen we itereren over een lijst
<ul>
  <% persons.forEach(function(person) { %>
    <li><%= person.firstname %> <%= person.lastname %></li>
  <% }); %>
</ul>
<ul>
  <% for (let i=0; i < persons.length; i++) { %>
    <li><%= persons[i].firstname %></li>
  <% } %>
</ul>
  • Of je kan ook een gewone for loop schrijven

HTML-escaping

  • Wanneer we <%- gebruiken als opening tag dan zal de tekst niet omgevormd worden door tekens die "escaped" zijn. 
  • Je kan in deze tool eens bekijken hoe je speciale tekens "escaped" worden.

Handige tags: Cheat Sheet

  • <%= %>: Outputs the value into the template (HTML escaped).
  • <%- %>: Outputs the unescaped value into the template.
  • <% %>: Executes JavaScript code without outputting anything.
  • <% for(var i=0; i < items.length; i++) { %>: Iterates over an array.
  • <% items.forEach(function(item){ %>: Iterates over an array 
  • <% if (user) { %>: Conditional statement.
  • <% } %>: Closes an opened JavaScript block.
  • <% include('filename') %>: Includes another EJS template.
  • <% include('filename', { quote: "Get it done!" }) %>:
    Includes another EJS template and passes additional data to it.

Onhandige structuren

Bij het genereren van HTML in EJS kom je vaak situaties tegen waarbij:

  • Code onnodig genest wordt door <% %>-blokken.
  • Vb basislogica moet implementeren
    • <% if(...) { %>  moet je openen
    • dan HTML moet printen
    • <% } %> dan de accolade weer moet sluiten
  • Dit kan soms de leesbaarheid beïnvloeden,
    zoals bij loops met condities.

📌 __append("...")

  • Wat doet __append("...")?

    • __append("...") voegt HTML of tekst rechtstreeks toe aan de uitvoer binnen een EJS-codeblock
    • Dit werkt binnen <% %>-blokken, waar normaal geen directe uitvoer mogelijk is.
    • Handig als je content wilt genereren in een
      conditie of loop zonder <%= %> te gebruiken.

📌 __append("...")

<ul>
  <% animals.forEach(animal => { %>
    <li><%= animal.name %> 
      <% if (animal.isEndangered) { %>
        <span style="color: red;"> 🔴 Bedreigd</span>
      <% } %>
    </li>
  <% }); %>
</ul>
<ul>
  <li>Tijger <span style="color: red;"> 🔴 Bedreigd</span></li>
  <li>Hond</li>
  <li>Panda <span style="color: red;"> 🔴 Bedreigd</span></li>
</ul>

Voorbeeld in EJS zonder __append()

Output als HTML

📌 __append("...")

<ul>
  <% animals.forEach(animal => { %>
    <li><%= animal.name %> 
      <% if (animal.isEndangered) { 
           __append('<span style="color: red;"> 🔴 Bedreigd</span>'); 
         } 
      %>
    </li>
  <% }); %>
</ul>
<ul>
  <li>Tijger <span style="color: red;"> 🔴 Bedreigd</span></li>
  <li>Hond</li>
  <li>Panda <span style="color: red;"> 🔴 Bedreigd</span></li>
</ul>

Voorbeeld in EJS met __append()

Zelfde output als HTML

📌 __append("...")

<ul>
  <% animals.forEach(animal => { 
       let output = `<li>${animal.name}`;
       if (animal.isEndangered) output += ' <span style="color: red;">🔴 Bedreigd</span>';
       output += '</li>';
       __append(output);
  }); %>
</ul>

Compacter met __append()

<ul>
  <% animals.forEach(animal => __append(`<li>${animal.name}${animal.isEndangered ? ' <span style="color: red;">🔴 Bedreigd</span>' : ''}</li>`)); %>
</ul>

Of NOG compacter!

📌 __append("...")

Wanneer is __append("...") handig?

  • Basislogica binnen een code bloack zoals
    • iteraties, condities, variabelen aanpassen.
  • HTML 'renderen' binnen een code block (zoals <%- ... ->)
  • Minder aparte code blocks

Gebruik het slim, maar met mate!

Te veel __append() kan je code minder leesbaar maken. 

  • Niet geschikt voor grotere HTML-structuren (minder leesbaar).

Welke data? Context Dumpen

Om alle context (de variabelen) in een EJS-template te dumpen, kun je de JSON.stringify() functie gebruiken om de volledige context als een JSON-object weer te geven.

Dit is handig voor debugging of om te zien welke data beschikbaar is in je template.

 

<pre><%= JSON.stringify(locals, null, 2) %></pre>

Lokale context dumpen

<pre><%= JSON.stringify(locals, null, 2) %></pre>
  • locals bevat alle variabelen die aan de template zijn doorgegeven.
  • JSON.stringify(locals, null, 2) zet de context om in een mooi opgemaakte JSON-string.
  • De <pre> tag zorgt ervoor dat de JSON netjes wordt weergegeven, met behoud van de tabs.

 

Dit toont alle context (de data) die je naar je EJS-template hebt gestuurd, in een leesbaar formaat binnen de browser.

 

⚠️ Let op: Zorg ervoor dat je dit alleen gebruikt in een ontwikkelomgeving, aangezien het potentieel gevoelige gegevens kan blootstellen!

Server Side Rendering met EJS

PARTIALS & LAYOUTS

EJS allows for template reuse through includes
(often referred to as "partials").

 

Partials are normal templates that may be called directly by other templates.

includes

  • Je kan een partial registreren door een nieuw .ejs bestand aan te maken in je /views/partials folder.
  • Vervolgens kan je je partial in je template gebruiken met:
<%- include('partials/yourFileName.ejs') %>

includes

  • EJS does not specifically support blocks, but layouts can be implemented by including headers and footers.
<%- include('partials/header'); -%>
<h1>
  Title
</h1>
<p>
  My page
</p>
<%- include('partials/footer'); -%>

Layouts in web development refer to reusable template structures that define the overall design and organization of a webpage.

 

They provide a consistent framework by including common elements like headers, footers, and navigation, allowing content to be dynamically injected while maintaining a uniform look across multiple pages.

Layouts

Terminologie:

- Partials: een deel van je website die op verschillende pagina's wordt hergebruikt (bv. navigatie, footer ...)
 

- Layouts: een abstracte schikking van de elementen op je website, (bv. holy grail lay-out), eventueel gebruikmakend van partials
 

- Pages: specifieke pagina's van je website, eventueel gebaseerd op een lay-out

Layouts: stap 1 | installatie

npm install express-ejs-layouts

express-ejs-layouts helpt bij het hergebruiken van een basislayout in een Express-app met EJS. Het voorkomt duplicatie van structuren, houdt de code overzichtelijk en maakt het beheren van pagina's efficiënter.

Layouts: stap 2 | configuratie

import express from "express";
import expressLayouts from "express-ejs-layouts";
// imports...

// ...

app.use(expressLayouts);
app.set("view engine", "ejs");
app.set("layout", "layouts/main");
app.set("views", "<pad-naar-views-folder>"); //

Koppel nu express-ejs-layouts
aan je gemaakte Express applicatie.

app.js

Layouts: stap 2 | configuratie

app.set("layout", "layouts/main");

Door een default layout-file te "setten", zal de inhoud steeds gerenderd worden binnen deze (default) structuur

Layouts: stap 3 | layout maken

<!DOCTYPE html>
<html lang="nl">
<head>
    <!-- META TAGS -->
</head>
<body>
    <%- include('../partials/navigation') %>

    <%- body %>

    <%- include('../partials/footer', { ...person }) %>
</body>
</html>

Maak een nieuw bestand layouts/main.ejs

📂 views/
│── 📂 layouts/
│   └── main.ejs   <-- Basislayout met <%- body %>
│── 📂 pages/
│   └── home.ejs   <-- Specifieke pagina die in de layout wordt geladen

Plaats de partials en een <%- body %> tag

Layouts: stap 3 | layout maken

In het layout-bestand van EJS zorgt <%- body %> ervoor dat de inhoud van de huidige pagina (view) dynamisch wordt ingevoegd op die plek.

 

  • <%- (met een streepje) betekent dat de inhoud ongeëscaped wordt gerenderd, wat betekent dat HTML-tags in de view correct worden weergegeven en niet als tekst.
  • body is de plek waar de specifieke pagina-inhoud terechtkomt binnen de layout.

 

Layouts: rendering flow

📂 views/
│── 📂 layouts/
│   └── main.ejs   <-- Basislayout met <%- body %>
│── 📂 pages/
│   └── home.ejs   <-- Specifieke pagina die in de layout wordt geladen
export const home = (req, res) => {
  const data = {
    title: "Yesterday is history. Today is a gift. Tomorrow is a mystery",
  };
  // render the home.hbs file when the /thisisatest route is accessed
  res.render("pages/home", data);
};

Voorbeeld

Template structuur

app.get("/", home);

// route

Layouts: rendering flow

export const home = (req, res) => {
  const data = {
    title: "Yesterday is history. Today is a gift. Tomorrow is a mystery",
  };
  // render the home.hbs file when the /thisisatest route is accessed
  res.render("pages/home", data);
};

Rendering Flow

  1. Express laadt pages/home.ejs en verwerkt de data.
  2. De layout layouts/main.ejs wordt toegepast,  waarbij

    <%- body %> wordt vervangen door de inhoud van home.ejs.
     
  3. HTML wordt gerenderd en naar de client gestuurd.

Layouts: rendering flow

Diagram van de rendering structuur

┌───────────────────────────┐
│ layouts/main.ejs          │
│ ┌───────────────────────┐ │
│ │  Header (vast)        │ │
│ ├───────────────────────┤ │
│ │  <%- body %>          │ │  ← Hier komt pages/home.ejs
│ ├───────────────────────┤ │
│ │  Footer (vast)        │ │
│ └───────────────────────┘ │
└───────────────────────────┘

home.ejs wordt ingesloten in main.ejs op de plek van <%- body %>, waardoor de uiteindelijke pagina een combinatie is van de layout en de specifieke pagina-inhoud

Layouts: meerdere layouts

Stel je hebt een tweede layout genaamd layouts/alternate.ejs en je wilt deze gebruiken voor de route /special.

// In je Express-app:
app.get("/special", (req, res) => {
  res.render("pages/special", { layout: "layouts/alternate", data: someData });
});
  • Globale layout: Voor andere routes zoals de homepage (/), zou de standaard layout layouts/main.ejs worden gebruikt, zoals je hebt ingesteld met app.set("layout", "layouts/main").
  • Specifieke layout voor de route: Voor de route /special wordt de layout layouts/alternate.ejs gebruikt. In dit geval overschrijf je de layout alleen voor deze route.

Layouts: meerdere layouts

┌────────────────────────────────────────┐
│              layouts/main.ejs          │ ← Standaard layout (voor andere routes)
│ ┌────────────────────────────────────┐ │
│ │ Header (vast)                      │ │
│ │ <%- body %>                        │ │ ← Pagina-inhoud (bijv. home.ejs)
│ │ Footer (vast)                      │ │
│ └────────────────────────────────────┘ │
└────────────────────────────────────────┘

┌────────────────────────────────────────┐
│           layouts/alternate.ejs        │ ← Specifieke layout voor /special
│ ┌────────────────────────────────────┐ │
│ │ Custom Header for Special          │ │
│ │ <%- body %>                        │ │ ← Pagina-inhoud (bijv. special.ejs)
│ │ Custom Footer for Special          │ │
│ └────────────────────────────────────┘ │
└────────────────────────────────────────┘

Server Side Rendering met EJS

STRUCTUUR UITBREIDEN

In onze app vind je afbeeldingen van dinosaurussen. Je kunt routes gebruiken om naar een overzicht van dinosaurussen te gaan en vervolgens door te klikken naar een detailpagina.

Een scenario, waar een aparte DinoController aangewezen is.

We hebben ook gedeelde data die in beide pagina's nodig is.

Gegevens uit data.js

  • Analyseer het bestand waar er json-data geëxporteerd wordt: src/data/data.js
  • Haal daar de array op met gegevens over 8 dino's, met volgende properties
    • id
    • title
    • slug
    • image ( vb /assets/images/dino_03.png)
    • description
  • De data wordt geëxporteerd via export const dinos ={...}

DinoController maken

  • Maak een DinoController aan, met daarin twee actions (a.k.a. methodes) die instaan voor afhandeling van express requests.


     
  • De index-methode zal instaan voor het overzicht van alle dino's, de detail voor de details van elke dino
export const index = (req, res) => {};
export const detail = (req, res) => {};
  • Importeer de dinosaurus-data in die controller

DinoController maken

  • ...
  • Importeer de dino-data in die controller
  • Render voor beide actions (index en detail) andere views uit. 
  • Je mag de "main" lay-out hergebruiken
     
  • Volgende stap:
    • routes opvangen via app.js
      (volgende slide)

Routes registreren

  • In app.js beschrijf je twee GET routes,
    die volgende url's opvangen
    • /dinosaurs
      --> naar index-methode uit DinoController
    • /dinosaurs/:slug
      --> naar detail-methode uit DinoController
       
  • ":slug" is een route parameter (link)
  • ​Test uit door naar de twee routes te gaan

Oefening 👨🏾‍💻

Server Side Rendering met EJS

HELPER METHODS

A helper function is a reusable utility that performs a specific task, making code cleaner, more modular, and easier to maintain. It simplifies complex operations and avoids repetition by encapsulating common logic.

Nuttige helpers

<h1>
  Welcome to De Groene Vallei, <%= uppie(dinosaur.name) %>!
</h1>
const helpers = {
  uppie: (str) => str.toUpperCase(),
  lowie: (str) => str.toLowerCase(),
  capitalize: (str) => str.charAt(0).toUpperCase() + str.slice(1),
  truncate: (str, length) =>
    str.length > length ? str.slice(0, length) + "..." : str,
};

export default helpers;

Maak een nieuw bestand utils/templateHelpers.js

Nuttige helpers

// ... imports
import helpers from "./utils/templateHelpers.js";

const app = express();

// ...

Object.assign(app.locals, helpers);

1. Importeer je helpers in app.js

2. Voeg ze toe aan de lokale context

Nuttige helpers

<h3><%= uppie(dinosaur.name) %></h3>

Gebruik

Aangezien de helper methodes in de locale context zitten, kan je ze direct gebruiken in je templates

Oefening  👩🏼‍💻

  • Maak een "emojify" helper die dit laat werken:
    • <%- emojify(variable, "classname") %>
    • met vb volgende outputs
      • <span class="highlight">👌 T-Rex</span>
      • <span class="featured">👌 Bronto</span>
  • Maak een helper die een button maakt
    • <a class="btn" href="...">Label</a>
  • Bedenk zelf ook een vb css(), js(), today(), ....
Made with Slides.com