Generate invoice PDF file using HTML template

28 August, 2023 4 min read

Today, I'll walk you through the process of generating an invoice PDF file for your backend app or custom script which could be run from the command line. Let's get started! 🎉

Introduction

Project structure

Let's assume we are creating a new project "generate-invoice", first Create a directory using the command mkdir generate-invoice. Then initialize a Node.js project by entering the command npm init and provide all the necessary information.

Now we need to install dependencies, which will be used. For that run the command npm i handlebars axios.

Having all the needed dependencies, let's create a conversion file using the command mkdir src && touch src/generate.js.

Now we need to store the template where the HTML code will be provided. Create a templates directory and invoice.html file for that with the command mkdir templates && touch templates/invoice.html.

This simple project structure is enough to start writing the code. Your project should look like this tree below.
├── package-lock.json
├── package.json
├── node_modules
├── src
│   └── generate.js
└── templates
    └── invoice.html

Prepare the template

Before diving into the conversion process, you'll need to create an HTML template for your invoice. This template will define the layout, content, and styling of your PDF invoice.

There are many invoice HTML templates you can find on Google. I am going to use an already created one, whose code source you can find here

In the end, it should look the following:

Invoice

Feel free to customize the HTML template to match your branding and invoicing needs. You can update fonts, and colours, add your company logo, and include any necessary details like invoice number, customer information, etc.

Put your template HTML code into the templates/invoice.html file and let's move to the next step.

Convert HTML to PDF

First, we need to add needed dependencies to the conversion script.

import fs from 'fs';
import Handlebars from 'handlebars';
import axios from 'axios';

Now we can load the invoice HTML code into the Handlebars template.

const invoice = fs.readFileSync('./assets/invoice.html', 'utf8');
const template = Handlebars.compile(invoice);

Also, we need to set parameters which will be binded into the template, they can come from various places in your real project. For example database, Excel file, command line arguments and so on. I will define those parameters in the script directly for simplicity.

const params = {
    invoiceNr: 'H2P202307-01711',
    billTo: 'Anna Smith',
    invoiceDate: (new Date()).toDateString(),
    paymentDue: (new Date()).toDateString(),
    orderId: 112233,
    items: [
        {description: 'Domain registration', quantity: 2, price: '$10', total: '$20'},
        {description: 'Web hosting', quantity: 1, price: '$15', total: '$15'},
        {description: 'Consulting', quantity: 1, price: '$500', total: '$500'},
    ],
    subTotal: '$535',
    tax: '$53.5',
    total: '$588.5',
};

Now we can process the template by passing the parameters and get the result - the final HTML code which will be converted to the PDF document.

const html = template(params);

There are multiple options for how to convert HTML to PDF, one could be by using open-source projects like Puppeteer or wkhtmltopdf. I wrote a separate post How to convert HTML to PDF using Puppeteer, but now for simplicity, I going to use html2pdf.app. Its free plan gives 100 credits per month, excellent!

To register and get the free API key use the link: https://dash.html2pdf.app/registration

Once you have an API Key, let's finish the conversion part.

const apiKey = '******************'; // API key from https://html2pdf.app

const response = await axios.post('https://api.html2pdf.app/v1/generate', {
    html,
    apiKey,
}, {responseType: 'arraybuffer'});
  

To send the HTML code to the PDF conversion API there are only a few lines of code. Now what needs to be done is to save the response. For that add the code line below to your script.

fs.writeFileSync('./assets/invoice.pdf', response.data);

The complete conversion script code looks as follows:

import fs from 'fs';
import Handlebars from 'handlebars';
import axios from 'axios';

const invoice = fs.readFileSync('./assets/invoice.html', 'utf8');
const template = Handlebars.compile(invoice);
const params = {
    invoiceNr: 'H2P202307-01711',
    billTo: 'Anna Smith',
    invoiceDate: (new Date()).toDateString(),
    paymentDue: (new Date()).toDateString(),
    orderId: 112233,
    items: [
        {description: 'Domain registration', quantity: 2, price: '$10', total: '$20'},
        {description: 'Web hosting', quantity: 1, price: '$15', total: '$15'},
        {description: 'Consulting', quantity: 1, price: '$500', total: '$500'},
    ],
    subTotal: '$535',
    tax: '$53.5',
    total: '$588.5',
};

const html = template(params);
const apiKey = 'd3a0264f70864...'; // get api key on https://html2pdf.app

const response = await axios.post('https://api.html2pdf.app/v1/generate', {
    html,
    apiKey,
}, {responseType: 'arraybuffer'});

fs.writeFileSync('./assets/invoice.pdf', response.data);

Conclusion

I like not to mix business logic or data with the presentation layer, and currently, there is a much clearer solution when we have a template separated from the data.

The simplicity of html2pdf.app API and its free plan allow us to save time and only with a few lines of code, we have the converted PDF file you can store it somewhere in the cloud or project directory.

Happy codding!