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! 🎉
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
.
├── package-lock.json
├── package.json
├── node_modules
├── src
│ └── generate.js
└── templates
└── invoice.html
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.
In the end, it should look the following:
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.
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!
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);
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!