Edward's Tech Site

this site made with Next.js 13, see the code

HOWTO: Jun 20, 2022 - EJS
Create a Node/Express/EJS site that displays various types of data
  • this howto shows you how to use the templating engine EJS to make multipage Node/Express server-side web sites
  • the EJS site we will build in this howto is based on this site: edwardtanguay.netlify.app/howtos?id=566
    • this site looks like this:
    • analyze the code of this site
    • it has an MVC pattern
      • the control (server.js) gets data from the module which fetches data from various sources (external API, local JSON file, Excel file, directory of images, and directory of md files)
      • this data is sent as an object to the view which displays the data
    • note that the view uses simple ES6 template string syntax
  • problem: while the above ES6 template string syntax can be a decent solution for simple one-page sites and can go a long way even if you have a nav bar with server-side pages, using a template engine solves many problems you would have to solve later
    • solution: the most popular server-side template engine for Node/Express sites is called EJS (i.e. Embedded JavaScript templates)
  • create new directory e.g. howto-node-express-ejs-mvc-site and copy in site from the above site's repository
  • swap out the template string syntax for EJS templating
    • set up basic EJS plumbing:
      • since this site has an MVC structure, we are essentially swapping out the View format, from ES6-template-strings to EJS
      • npm i ejs
      • create public/views/pages/index.ejs
        • test of <b>ejs</b>
      • in server.js
        • import path from 'path';
           
          const __dirname = path.resolve(path.dirname(''));
           
          app.set('view engine', 'ejs');
          app.et('views', path.join(__dirname, './public/views'));
           
          app.get('/', (req, res) => {
          res.render('pages/index')
          });
    • send in model data to the view as we did before
      • server.js
        • res.render('pages/index', siteData)
    • and display it in the view
      • index.ejs
        • test of <b>ejs</b>
          <hr />
          there are <%=nouns.length%> nouns
    • the site should now look like this:
    • recreate the former site in EJS syntax
      • <!DOCTYPE html>
        <html lang="en">
         
        <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="main.css" />
        <title>Info Site</title>
        </head>
         
        <body>
        <h1>Info Site</h1>
        <div>
        there are <%=nouns.length%> nouns
        </div>
        </body>
         
        </html>
    • the site should now look like this:
    • note that our MVC structure makes it easy to swap templating engines:
      • we simply swap the view component
      • data is still sent as object to the view as before
      • stylesheet is still used by the view in the same way as before
    • convert the ES6-template-string syntax to EJS syntax
      • convert this:
        • <h2><b>API call: </b>There are ${nouns.length} nouns.</h2>
          <div class="content">
          ${nouns
          .filter((m, i) => i < 3)
          .map((m) => m.article + ' ' + m.singular)
          .join(', ')}, ...
          </div>
      • to this:
        • <h2><b>API call: </b>There are <%=nouns.length%> nouns.</h2>
          <div class="content">
          <ul>
          <% for(const noun of nouns.slice(0,3)) { %>
          <li><%=noun.article%> <%=noun.singular%></li>
          <%}%>
          <li>...</li>
          </ul>
          </div>
    • convert rest of site:
      • <h2><b>Local JSON file: </b>There are <%=books.length%> books.</h2>
        <div class="content">
        <ul>
        <% for(const
        book
        of
        books.slice(0,3))
        {
        %>
        <li>
        <%=book.title%>
        </li>
        <%}%>
        <li>...</li>
        </ul>
        </div>
         
        <h2><b>Local Excel file: </b>There are <%=translations.length%> translations.</h2>
        <div class="content">
        <ul>
        <% for(const
        translation
        of
        translations.slice(0,3))
        {
        %>
        <li>
        <%=translation.fromPhrase%>
        </li>
        <%}%>
        <li>...</li>
        </ul>
        </div>
         
        <h2><b>Local directory of images: </b>There are <%=landscapePhotos.length%> landscape photos.</h2>
        <div class="content">
        <% for(const
        fileName
        of
        landscapePhotos)
        {
        %>
        <img src="images/<%=fileName%>" />
        <%}%>
        </div>
         
        <h2><b>Local directory of markdown files: </b>There are <%=jobs.length%> jobs.</h2>
        <div class="content">
        <% for(const
        job
        of
        jobs)
        {
        %>
        <div class="job">
        ID-CODE: <%=job.idCode%>
        <%-job.html%>
        </div>
        <%}%>
        </div>
    • note in particular this syntax:
      • <img src="images/<%=fileName%>" />
         
        <%-job.html%>
  • make nouns pages
    • server.js
      • app.get('/nouns', (req, res) => {
        res.render('pages/nouns', siteData)
        });
    • views/pages/nouns.ejs
      • <!DOCTYPE html>
        <html lang="en">
         
        <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="main.css" />
        <title>Info Site</title>
        </head>
         
        <body>
        <h1>Info Site</h1>
         
        <h2><b>API call: </b>There are <%=nouns.length%> nouns.</h2>
        <div class="content">
        <ul>
        <% for(const
        noun
        of
        nouns.slice(0,3))
        {
        %>
        <li>
        <%=noun.article%>
        <%=noun.singular%>
        </li>
        <%}%>
        <li>...</li>
        </ul>
        </div>
         
        </body>
         
        </html>
  • make header with nav bar with welcome and noun page
    • views/partials/header.ejs
      • <!DOCTYPE html>
        <html lang="en">
         
        <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="main.css" />
        <title>Info Site</title>
        </head>
         
        <body>
        <h1>Information Site</h1>
        <nav>
        <a href="/">welcome</a> |
        <a href="nouns">nouns</a>
        </nav>
    • views/partials/footer.ejs
      • </body>
         
        </html>
    • views/pages/nouns.ejs
      • <%- include('../partials/header');%>
        <h2><b>API call: </b>There are <%=nouns.length%> nouns.</h2>
        <div class="content">
        <ul>
        <% for(const
        noun
        of
        nouns.slice(0,3))
        {
        %>
        <li>
        <%=noun.article%>
        <%=noun.singular%>
        </li>
        <%}%>
        <li>...</li>
        </ul>
        </div>
        <%- include('../partials/footer');%>
    • main.css
      • nav a {
        text-decoration: none;
        color: orange;
        }
  • create rest of pages
  • publish site to Heroku
    • set up for Heroku
      • make sure start runs node:
        • "scripts": {
          "dev": "nodemon server.js",
          "start": "node server.js"
          },
      • makes sure server uses dynamic port
        • const port = process.env.PORT || 3007;
    • log into: ../heroku.comhttps://heroku.com
    • new app, give name, Europe
    • [GitHub]
    • [Connect to GitHub]
    • search for repository, click [Connect]
    • [Enable Automatic Deploys]
    • [Overview]
    • [Open app]
    • to debug: heroku logs -a howto-node-express-ejs-mvc --tail