SeriousLee
SeriousLee

Reputation: 1371

Node js - Compile handlebars view without rendering it

My problem is simple: I want to compile a Handlebars view then not render it - instead I want to use the compiled HTML server side. While researching the problem, this was the closest to my problem that I could find, but unless I'm misunderstanding, all their doing in this question is creating an HTML string server-side and populating it with data. It's insufficient because I don't want to create any HTML strings, I literally want to keep my HTML stored in .hbs files, as usual, and then compile a view (i.e. send it my variables), then instead of displaying it to a user (which won't be possible anyway because it's an API server only), I want to have the compiled HTML to send it on to Mailgun (but the fact that I want to send it to Mailgun is outside the scope, answers should preferably only tell me how to compile the view and be able to (for instance) console.log() the HTML).

I'm using Node v9.8.0

Packages:

Let me know if I need to supply any additional information and the reason that I haven't included any example code of what I've tried is that I have literally no idea what to even try. All I've tried was let emailHtml = res.render('email/contactReceipt.hbs', { name:req.body.name }) inside my route but that gave me the error Cannot set headers after they are sent to the client.

Side Note: I am fairly new to Node.js and it's not part of my normal stack. I have noticed that other people use the package 'express-handlebars' and even just 'handlebars' so if what I'm using ('hbs') is actually in no way related to Handlebars, or if I won't be able to accomplish what I'm trying to do because of it, let me know.

Upvotes: 2

Views: 5188

Answers (1)

Marcos Casagrande
Marcos Casagrande

Reputation: 40404

res.render accepts a third parameter which is a callback containing the rendered string, without sending a response, thus avoiding Headers already sent error

res.render(view [, locals] [, callback])

Renders a view and sends the rendered HTML string to the client. Optional parameters:

  • locals, an object whose properties define local variables for the view.
  • callback, a callback function. If provided, the method returns both the possible error and rendered string, but does not perform an automated response. When an error occurs, the method invokes next(err) internally.
res.render('email/contactReceipt.hbs', { name:req.body.name }, (err, html) => {
    if(err) 
       return console.error(err);
    // do whatever you want here with `html`
});

You can also use app.render which is similar to res.render but works on a global level, and always takes a third parameter.

app.render('email/contactReceipt.hbs', { name:req.body.name }, (err, html) => {
    if(err) 
       return console.error(err);
    // do whatever you want here with `html`
});

Or you can just compile the template directly and do whatever you want later.

const hbs = require('hbs');

const template = hbs.compile('<h1>{{title}}</h1>');

const html = template({ title: 'Handlebars' });
console.log(html); // <h1>Handlebars</h1>

If you want to read the template content from disk, just use the fs.readFile and pass the content to hbs.compile

const hbs = require('hbs');
const fs = require('fs');
const readFile = require('util').promisify(fs.readFile);

async render(file, data) {
    const content = await readFile(file, 'utf8');
    // Implement cache if you want
    const template = hbs.compile(content);

    return template(data);
}

Upvotes: 11

Related Questions