How to Generate a PDF in Node.js From HTML

Sharing is Caring

In this article, you’ll learn how to generate a PDF directly from within a web application using HTML and JavaScript/Node.js . This article is really closely associated with the blog article: Generating a PDF in Node.js because it’s the alternative option.

The PDF format is one of the most common file formats for transmitting static information. It’s a really common request for businesses to be able to export a document or data into a really well-formatted PDF file whether it be an invoice or a report.

You could generate a PDF file using a library that outputs the PDF using straight JavaScript and generate a pretty sophisticated file but it’s a lot easier to do it with HTML.

Why generate a PDF from HTML

One of the beautiful things about HTML is that it is a document mark-up language right out of the box. It also can use CSS which has a great specification for dealing with print.

For the most part, generating documents is easier and faster with HTML than it will be with JavaScript. It might even be easy enough that folks in marketing can do it without much development help needed!

There are some pretty serious things to keep in mind though:

  • Pages have to be manually constrained
  • A lot of CSS media properties aren’t available
  • A lot of developer tools won’t be able to debug problems once the PDF is compiled.
  • A lot of these libraries are pretty large because they run Chrome or another browser in headless mode. Puppeteer is a great library but it’s also very large.
  • Long or complicated reports can take quite a while to generate.

What you will learn from this blog article

By the end of this blog article, you should be able to generate a really basic invoice that would allow someone to easily understand what they are buying.

We will build an invoice using HTML, JavaScript, and Node.js. Node.js will act as our server. We will build an invoice because it’s pretty complex and is likely something you could have to build in your time as a developer.

The PDF file should be selectable and have searchable text so we can’t simply take a screenshot of the DOM.

Choosing a library

One of the most important parts of every task is choosing a library or framework that we build off of. It is rarely the right solution to build our own framework or library.

When choosing a library we need to be really careful about making sure that it’s still maintained, it’s license is pretty permissive, and that it has a good reputation. It’s really important that we pay attention to major dependencies too as often one becomes deprecated and the library ends up abandoned as a result.

For example: The html-pdf module is a fantastic library but it depends on PhantomJS which was deprecated about four years ago. This is really unfortunate because its’ an amazing library and the author hasn’t updated it since then.

Usually, we won’t find a library that does exactly what we need so we might need to combine two different things to produce the final result that we need. For example, there are very few libraries that would have a templating language built-in and be able to produce a pdf as the final result.

I wasn’t able to find an npm library that did exactly what I want so we’ll actually use Handlebars and Puppeteer together to generate the html and the pdf.

Handlebars and Mustache together are a great templating language. I’ve used Handbars a lot in the past for outputting csv files, text files, and even html.

Our process at a relatively high level will look something like this:

What we’ll need for the demo

  • A decent computer with a decent amount of CPU and RAM.
  • Access to a computer running at least Node 10 and NPM 6.
  • VS Code or another editor

You can see what version of Node you are using by going to the command prompt / terminal and typing

node --version
Node version from command prompt

As you can see, I am using Node version 12.18.4

I am using Visual Studio Code as I find it works pretty well, doesn’t cost any money, and works better than Visual Studio did for writing JavaScript. If you’re interested, you can read my blog post Why I love VSCode.

In our existing project, we’ll need to install Handlebars and Puppeteer .

Handlebars

Handlebars is a really fancy templating framework that allows us to do components and provide us with some built-in helper functionality.

We can load a template and compile it using Handlebars. Here’s an example of how we would do this in Node.js.

We can create a handlebars file that would look something like this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />  
    <title>Something awesome</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
  </head>
  <body>
    My name is {{{name}}}
  </body>
</html>

Next, we need to write our actual code that will use the template.

const handlebars = require('handlebars')
const template = fs.readFileSync('./template.hbs', 'utf8')
const compiledTemplate = handlebars.compile(template)

Puppeteer

Puppeteer is a node library that allows us to use a headless Chrome instance to generate the report. Puppeteer is really neat and works by using a high level API through the DevTools protocol.

Set up Puppeteer to open a new browser window and navigate to the compiled template.

const puppeteer = require('puppeteer')

(async () => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()
  await page.goto(`data:text/html,${compiledTemplate(data)}`, {
    waitUntil: 'networkidle0'
  })

Use Puppeteer’s page.pdf() method to generate the PDF.

 const pdf = await page.pdf({
    format: 'A4',
    printBackground: true
  })

  await browser.close()

  // Do something with the PDF, like save it to a file or send it as a response
})()

Replace the variables in the template with data using the compiledTemplate() function, passing in an object containing the data as an argument.

For example, if your template contains the variable {{name}}, you can replace it with a specific value by passing an object like this to the compiledTemplate() function:

const data = {
  name: 'John Smith'
}

const html = compiledTemplate(data)

This will generate an HTML string with {{name}} replaced with John Smith.

Wrapping it Up

In this blog post, we learned how to generate PDF from an html file using Puppeteer and Handlebars.

I hope this helps! Let me know if you have any questions.


Also published on Medium.

Sharing is Caring

Brian is a software architect and technology leader living in Niagara Falls with 13+ years of development experience. He is passionate about automation, business process re-engineering, and building a better tomorrow.

Brian is a proud father of four: two boys, and two girls and has been happily married to Crystal for more than ten years. From time to time, Brian may post about his faith, his family, and definitely about technology.