DEV Community

Cover image for new Async Template Engine for Express.js
Tobias Nickel
Tobias Nickel

Posted on

new Async Template Engine for Express.js

Do we still need templating engines such as ejs, jade, handlebars or nunchucks? In javascript we love writing asyncronous code. Do these templating engines still fulfill the needs of modern javascript?

All the way back, when making the switch from php or ruby to node.js, many of us learned how to make an app with express.js.

Implementing route handlers, that in the end call the response.render() method. Passing in a chunk of data and the configured template engine would create the html string that is returned to the client. It followed the all mighty MVC pattern. That basically all frameworks used. In todays apps, the view got replaced by react or vue. And more recently with next.js and nuxt.js these can render in serversite #ssr.

Templating Engines are good to write markup in a structured way. To render data that is passed in. And compose templates by including or extending other template files.

Today the situation has changed a little and we have a powerfull alternative. We have template string literals in Javascript. And I believe with a little helper function that I will show you below, some very important points can be improved.

The issue with template engines is, that they are pure sync functions. Our node.js code, has to prepare all data, that the template needs for its arguments. This can lead to unnecessary overfetching. Not overfetching of data to the client, but to load more data from the database than needed in the template.

Next I show you the little helper, that will allow to render temlates asyncronously.

async function t(strings, ...data) {
  const resolvedData = await Promise.all(data.map((d)=> {
    if (typeof d === 'function') {
      return d();
    } else {
      return d;
    }
  });
  resolvedData.push('');
  return strings.map(
    (s, i) => s + resolvedData[i]
  ).join('')
}
Enter fullscreen mode Exit fullscreen mode

You see, it is a function that can be used as template tags. All the parameter that are passed in, get resolved to a string.

Examples

Next we will see how we can use this template helper to include and extend templates as well as see how the exact needed data is fetched from database.

  • Extendable page or layout

These kind of template functions usually take some arguments and drop them into place.

function page ({title, meta, headers, content}) {
  return t`<!DOCTYPE html>
  <html>
    <head>
      <title>${title}</title>
      ${metatags(meta)}
      ${headers}
    </head>
    <body>${content}</body>
  </html>
  `;
}
Enter fullscreen mode Exit fullscreen mode
  • Includable

Often they are reusable common components, such as buttons, cards, or like in this case, a list of meta tags. I used the word comonents here very deliberate.

function metatags(metaData) {
  return Object.keys(metaDats || {}).map(
    key => `<meta name="${key}" content="${metaData[key]}" />`
  ).join('')
}
Enter fullscreen mode Exit fullscreen mode
  • template for a complete article page

It loads some data and fill it into a page. The articles are queried by id, using SQL, mongo or any rest or other service, its up to you.

async function article(id) {
  const article = await Articles.getById(id);
  return page({
    title: article.title,
    meta: {
      keywords: artitle.keywords,
      description: article.description
    },
    content: article.text,
  });
}
Enter fullscreen mode Exit fullscreen mode
  • build the app

Here an express js app is shown, but it could be any server. Koa, fastify, you name it. It handles the http. It does not load data simple return the response.

const express = require('express');
const app = express();
app.listen(process.env.PORT || 3000);

app.get('/artitle/:id', async ({params})=>{
  res.header('Content-Type', 'text/html');
  res.send(await artitle(params.id));
});
Enter fullscreen mode Exit fullscreen mode

ohh, did I omit the controller? You need some more logic? Put it where ever you see fit. You want some dependency injection? We can pass a context object to the article template, for example the request object. That can have all the additions from middlewares.

We can name this pattern the MV pattern. As we made the controllers optional. Isn't it very simple to select the template for an API and return to the user?

Conclusion

The tips in this article are not all very serious. But I hope you enjoyed the little examples. And for small weekend projects, why not try something new? have fun 😉

Top comments (0)