HTML to PDF Node app

Small app that helps you create PDF files out of HTML, supporting CSS

I had this idea for creating a NodeJS app for generating PDF out of HTML. The result is a pretty small app I put together with the help of Chat-GPT. One of the main reasons I wrote this blog post is for pointing out how helpful AI tools can be when it comes to bringing ideas to life and also learning how to tackle such tasks.

I didn't know where to start with this app

And that is where Chat-GPT acted like a consultant. We will be using puppeteer and fs (NodeJS file system module) to read the content from our HTML file, then kind of mocking a web scraping functionality to read it and then converting it to PDF.

The functionality stated above is pretty basic when it comes to using these libraries/APIs, but instead of crawling online pages, we can create our own HTML file, add some CSS styles to it and then generate the PDF.

Let's get started

Setting up the project

Create a folder named whatever you like then open it in your IDE. I am using VS Code. Open up the terminal and let's initialize our project. Type the following:

npm init -y

This will create the package.json file that is containing our project's information along with the dependencies we are going to use. Let's add these dependencies. Type the following in your terminal:

npm i puppeteer uuid

Puppeteer is one of the most powerful NodeJS libraries giving you access to APIs for handling different tasks on a Chrome/Chromium browser. Check it out here. uuid is for creating unique ids for the HTML pages we are going to open in our browser for crawling. We will be mainly using it to avoid collisions with system files or when running our app simultaneously from different ports.

Creating the files

Inside your directory, create a app.js file. This is the file containing all of our app's functionality. Once created, paste the following code in it:

const puppeteer = require("puppeteer");
const fs = require("fs");
const path = require("path");
const os = require("os");
const { v4: uuidv4 } = require("uuid");

async function htmlToPdf(htmlFilePath, pdfFilePath, options = {}) {
  try {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();

    // Convert relative image paths to absolute
    let content = fs.readFileSync(htmlFilePath, "utf8");
    content = content.replace(/src="([^"]+)"/g, (match, p1) => {
      if (p1.startsWith("http") || p1.startsWith("file:///")) {
        return match; // Ignore absolute URLs and file URLs
      }
      const imagePath = path.join(__dirname, p1);
      return `src="file://${imagePath}"`;
    });

    // Write the modified content to a temporary file
    const tempHtmlPath = path.join(os.tmpdir(), `${uuidv4()}.html`);
    fs.writeFileSync(tempHtmlPath, content);

    // Use page.goto() to load the HTML file
    await page.goto(`file://${tempHtmlPath}`, { waitUntil: "networkidle0" });

    const pdfOptions = { format: "A4", ...options };
    await page.pdf({ path: pdfFilePath, ...pdfOptions });

    await browser.close();
    console.log(`PDF generated: ${pdfFilePath}`);
  } catch (error) {
    console.error(`Error generating PDF: ${error.message}`);
  }
}

// Example usage
const htmlFilePath = path.join(__dirname, "index.html");
const pdfFilePath = path.join(__dirname, "output.pdf");

htmlToPdf(htmlFilePath, pdfFilePath);

That is all it takes! This app will look for an index.html file at the root of our app and create a PDF file out of it! Create the HTML file at the root of your project directory and copy the following sample code for you to test it out:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Original Prompt</title>
    <link
      href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap"
      rel="stylesheet"
    />

    <style>
      body {
        font-family: "Roboto", sans-serif;
        line-height: 1.6;
      }

      .img-header {
        width: 100%;
        height: 500px;
      }
    </style>
  </head>
  <body>
    <img src="/149.jpg" class="img-header" alt="" />
    <h2 style="color: green">Title:</h2>
    <p style="font-weight: bold">Easily create pdfs out of HTML</p>

    <h2 style="color: green">It's pretty easy</h2>
    <ol>
      <li>
        <strong>Create lists</strong>
        <ul>
          <li>This is the first list item.</li>
          <li>And of course this is the 2nd.</li>
        </ul>
      </li>
      <li>
        <strong>I am a list item outside of the original list.</strong>...
      </li>
    </ol>

    <h2 style="color: green">Using AI</h2>
    <p>
      <span style="font-weight: bold"
        >It would take me a lot of hours building this simple functionality.
        First of all I would have to read a lot of the docs of Puppeteer</span
      >
      It is pretty great being able to bring our ideas faster to life with tools
      that use AI to generate code.
    </p>
  </body>
</html>

Notice that on the HTML file above I included a Google font and an image. The image "src" is looking for the specified file at the root of the directory too. You can create a separate folder for your images if you need to. Just showing you that it works!

Time to test our app. Open your terminal once again and run our app like so:

node app.js

After some time (depending on the size of your HTML file), you will see an output.pdf file. Open it to see your HTML file in a PDF format.

End notes

I created this small app using Chat-GPT which saved me a lot of time from reading the docs while also learning how to create such an app. As long as you understand what your code does and being able to debug or add new functionalities/features to it, AI tools can help you move fast without really breaking anything or leaving you clueless as to what your code does.

Hope you found this useful, thanks!