PDF Sample applications Tech

How to Create a PDF Invoicing Web Application

by PDF SDK | September 2, 2020

Getting paid is one of the most critical functions in any business, and digital invoices are becoming standard practice in a wide swath of industries. With this in mind, web application developers are often tasked with generating and sending PDF invoices programmatically.

Whether you’re automating the invoice generation and notification process or building a GUI that allows your team to proactively remind clients about outstanding invoices, the first technical hurdle you’ll face is generating a PDF invoice. While you could write a custom PDF generation script, that’s a huge undertaking. Web-based services are convenient, but if you have confidentiality agreements with your clients, sending data to a third-party service over the internet might be a problem for you.

Fortunately, Foxit’s PDF library allow you to quickly and securely generate PDF files. Using our HTML to PDF converter, you can convert any HTML document – including invoices – to a PDF file that you can attach to an email or allow clients to download from your web application.

In this tutorial, you’ll see how to create a NodeJS application that uses the Foxit PDF SDK to generate PDF invoices from HTML invoices in a web app. Once created, you’ll use Nodemailer to send the invoice via SMTP to the client’s email address. You can follow each step below or download the finished codebase on Foxit’s GitHub.

Building a Web Application to Create and Send PDF Invoices

In this tutorial, you’ll create an internal tool to help your billing department follow up on unpaid invoices. You’ll also create a page that lists all outstanding invoices and a page to preview each of them. Users will be able to click a link to send an email reminder to each client with the invoice attached.

You’ll use the Express web framework, Pure CSS for styling, and Nodemailer to send emails.

Prerequisites

NodeJS version 8+ and NPM version 5+
The Foxit SDK (a free trial download is available here)
The Foxit [HTML to PDF conversion add-on]

Mailtrap (if you’d like to test SMTP email transport)

Creating a New Express App

To create a new boilerplate Express web application, use the app generator:

npx express-generator --git --view=hbs

This will create a web app with a `.gitignore` file and Handlebars template files.
Next, add the Nodemailer npm package and install Express’ dependencies:

npm i nodemailer && npm i

The default application generated by Express comes with two route files: `/routes/index.js` and `/routes/users.js`. Remove the `users.js` route and create a new route file called `invoices.js`. Add this new route to your `app.js` file and remove the `usersRoute`:

...
var indexRouter = require('./routes/index');
var invoicesRouter = require('./routes/invoices');
var app = express();
...
app.use('/', indexRouter);
app.use('/invoices', invoicesRouter);
...

The invoices router is where you’ll do the bulk of the work in this application.

Before you create the route, you’ll need some data. In a real application, you’ll likely connect to a database, but for demonstration purposes, you will add your invoice data to a JSON file.

Create a new file at `/data/invoices.json` and add the following:

[
{
"id": "47427759-9362-4f8e-bfe4-2d3733534e83",
"customer": "Bins and Sons",
"contact_name": "Verne McKim",
"contact_email": "[email protected]",
"address": "3 Burning Wood Street",
"city_state": "Memphis, TN 38118",
"plan_id": "41595-5514",
"plan_name": "Starter",
"subtotal": 499.99,
"fee": 50.00,
"total": 549.99
},
{
"id": "1afdd2fa-6353-437c-a923-e43baac506f4",
customer": "Koepp Group",
"contact_name": "Junia Pretious",
"contact_email": "[email protected]",
"address": "7170 Fairfield Hill",
"city_state": "Los Angeles, CA 90026",
"plan_id": "43419-355",
"plan_name": "Professional",
"amount": 999.99,
"fee": 50.00,
"total": 1049.99
},
{
"id": "59c216f8-7471-4ec2-a527-ab3641dc49aa",
"customer": "Lynch-Bednar",
"contact_name": "Evelin Stollenberg",
"contact_email": "[email protected]",
"address": "9951 Erie Place",
"city_state": "Chicago, IL 60605",
"plan_id": "63323-714",
"plan_name": "Starter",
"amount": 499.99,
"fee": 50.00,
"total": 549.99
}
]

These three invoices contain customer, plan, and billing data that will help you generate an invoice in the next section.

Creating the Invoices Routes

The `routes/invoices.js` file will create three new routes in your application:

  • `/invoices` – A list of all the invoices from the flat data file above.
  • `/invoices/:id` – An invoice preview so users can see what the invoice will look like before sending it to the client.
  • `/invoices/:id/email` – An endpoint that generates and sends the PDF invoice to the contact email on file.

The last route will be addressed later, but you can start by adding the first two routes. Open the `invoices.js` file and add the following:

const express = require('express');
const router = express.Router();
const invoices = require('../data/invoices.json');

// Import exec to run the Foxit HTML to PDF executable
const { exec } = require('child_process');

// Import nodemailer to send emails
const nodemailer = require('nodemailer');
router.get('/', function(req, res) {
res.render('invoice-list', {
invoices: invoices,

// Accepts errors and successes as query string arguments
success: req.query['success'],
error: req.query['error'],
});
});
router.get('/:id', function(req, res) {
const invoice = invoices.find(invoice => invoice.id === req.params['id']);

// If the invoice doesn't exist, redirect the user back to the list page
if (!invoice) {
res.redirect('/invoices');
}

// Make the date format pretty
const date = new Date().toLocaleDateString("en", {
year:"numeric",
day:"2-digit",
month:"2-digit",
});
res.render('invoice-single', { invoice, date });
});
router.get('/:id/email', function(req, res) {

// Coming soon.
});
module.exports = router;

Your application is almost ready to test, but first, you need to create the two view files.

Adding Views and Styles

Express separates logic and presentation into `routes/` and `views/`. Add two new files to the `views/` directory: `invoice-list.hbs` and `invoice-single.hbs`.

Add the following to your `invoice-list.hbs` file:

<h1><a href="/invoices">Unpaid Invoices</a></h1>
{{#if success}}
<p class="success"><strong>Success!</strong> The invoice has been sent to the client.</p>
{{/if}}
{{#if error}}
<p class="error"><strong>Whoops!</strong> Something went wrong and your invoice could not be sent.</p>
{{/if}}
{{#each invoices}}
<h3>{{this.customer}}</h3>
<p>ID: {{this.id}} <br/>
<a href="/invoices/{{this.id}}">View</a> | <a href="/invoices/{{this.id}}/email">Email Reminder</a>
</p>
{{/each}}

Open the `invoice-single.hbs` file and add this:

<div class="pure-g">
<div class="pure-u-1-2">
<h1>Invoice</h1>
</div>
<div class="pure-u-1-2" style="text-align: right;">
<p class="muted">Issued on {{ date }}</p>
</div>
</div>
<div class="pure-g">
<div class="pure-u-1-2">
<h3>Provider</h3>
<p>
<strong>Tiller, Inc.</strong><br/>
1255 S. Clark<br/>
Chicago, IL 60608
</p>
</div>
<div class="pure-u-1-2" style="text-align: right;">
<h3>Billed to</h3>
<p>
<strong>{{invoice.customer}}</strong><br/>
{{invoice.contact_name}}<br/>
{{invoice.address}}<br/>
{{invoice.city_state}}
</p>
</div>
</div>
<table class="pure-table pure-table-horizontal">
<thead>
<tr>
<th>ID</th>
<th>Plan Name</th>
<th class="text-right">Amount</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{invoice.plan_id}}</td>
<td>{{invoice.plan_name}}</td>
<td class="text-right">${{invoice.subtotal}}</td>
</tr>
<tr>
<td></td>
<td class="text-right">Subtotal:</td>
<td class="text-right">${{invoice.subtotal}}</td>
</tr>
<tr>
<td></td>
<td class="text-right">Taxes and Fees:</td>
<td class="text-right">${{invoice.fee}}</td>
</tr>
<tr class="bold">
<td></td>
<td class="text-right">Total:</td>
<td class="text-right">${{invoice.total}}</td>
</tr>
</tbody>
</table>
<div class="footer">
<p>Please make checks payable to <strong>Tiller, Inc</strong>. Invoices are due 30 days after date issued.</p>
<p>Thank you for your business!</p>
</div>

Next, you’ll need to add styles to your app’s stylesheet and load the Pure CSS module to make it look nice. Open the `views/layout.hbs` file and replace it with the following to import Pure and create a single column grid layout:

<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/build/pure-min.css" integrity="sha384-cg6SkqEOCV1NbJoCu11+bm0NvBRc8IYLRGXkmNrqUBfTjmMYwNKPWBTIKyw9mHNJ" crossorigin="anonymous">
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<div class="container">
<div class="pure-g">
<div class="pure-u-1">
{{{body}}}
</div>
</div>
</div>
</body>
</html>

Open your application’s `public/style.css` file and add the following:

body {
background-color: #f7f7f7;
color: #333333;
}
a {
color: #156d6a;
}
h1 a,
h2 a,
h3 a {
text-decoration: none;
}
table {
width: 100%;
}
.container {
background-color: #ffffff;
max-width: 700px;
margin: 0 auto;
padding: 30px;
}
.muted {
color: #999999;
}
.bold {
font-weight: bold;
}
.text-right {
text-align: right;
}
.footer p {
margin-top: 30px;
}
.success {
background-color: #c0f5f3;
color: #0d928d;
padding: 10px;
}
.error {
background-color: #f5c0c0;
color: #792525;
padding: 10px;
}

While you don’t have to add styles, it will make your invoices look more professional as Foxit captures all the styling in your HTML document when it generates PDFs.

At this point, you are ready to test your application. From the command line, run `npm start` and open your web browser to `localhost:3000/invoices`. You should see a list of invoices like this:

Click “View” to preview each invoice:

In the last two steps, you’ll use the Foxit HTML to PDF tool to generate PDF invoices before you send attach them to an email using Nodemailer.

Generating PDFs with Foxit

You can use Foxit’s SDK for a wide variety of PDF creation and manipulation operations, but one common use case is generating a PDF file from an HTML document or URL. The process of downloading and compiling the HTML to PDF executable is documented here. Once you’ve successfully run the demo from your command line, you can proceed.

Node’s `child_process` library includes a function called `exec()` that allows you to execute a command-line function. This is a convenient method for running Foxit executables written in C++. To run the HTML to PDF executable, update your `/:id/email` route by replacing it with the following:

...
router.get('/:id/email', function(req, res) {
// Set the executable path and output folder
const htmlToPdfPath = '/path/to/foxit/html2pdf';
const outputFolder = __dirname + '/../invoices/';
// Get the invoice
const invoice = invoices.find(invoice => invoice.id === req.params['id']);
if (!invoice) {
res.redirect('/invoices?error=1');
}
// Convert the HTML to PDF
exec(
`${htmlToPdfPath} -html /invoices/${req.params['id']} -o ${outputFolder}${req.params['id']}.pdf`,
(err, stdout, stderr) => {
if (err || stderr) {
console.error(err, stderr);
res.redirect('/invoices?error=1');
} else {
// For now: log the output file path
console.log(`PDF generated and saved to ${outputFolder}${req.params['id']}.pdf`);
res.redirect('/invoices?success=1');
}
});
});

Before you run this code, be sure to update the `htmlToPdfPath` to point to your `htmltopdf` executable.

Go back to your list of invoices and click “Email Reminder” on any of the invoices, the Node app will call the `htmltopdf` executable. The executable will, in turn, convert your invoice from the HTML document served by Express to a PDF file. You can find the PDF file in your web application’s `invoices/` directory.

Now that you can generate PDF invoices, the last step is to send these invoices to your customers.

Sending Emails with Nodemailer

Nodemailer provides a convenient interface to access many email transport layers. SMTP is one of the most prevalent, but you could also use Amazon SES or your server’s `sendmail` command.

To test Nodemailer, you can use the stream transport’s JSON option, which lets you log the message to your console. To set up your message and send it with Nodemailer, add the following just below the “PDF generated and saved to…” `console.log` statement in your `/invoices/:id/email` route:

...
// Construct the message
const message = {
from: '[email protected]',
to: invoice.contact_email,
subject: 'Reminder: Your Invoice from Tiller, Inc. is Due',
html: `<p>Hey ${invoice.contact_name},</p><p>I just wanted to remind you that your invoice for last month's services is now due. I've attached it here for your convenience.</p><p>Thanks for your business!</p>`,
attachments: [
{
filename: 'invoice.pdf',
path: `${outputFolder}${req.params['id']}.pdf`,
}
]
};
// Use mailer to send invoice
nodemailer
.createTransport({jsonTransport: true})
.sendMail(message, function (err, info) {
if (err) {
res.redirect('/invoices?error=1');
} else {
console.log(info.message);
res.redirect('/invoices?success=1');
}
});
...

Refresh your Node application and click “Email Reminder” on any of the invoices. This time, you’ll see the entire email data object as JSON in your console:

{
"from": {
"address": "[email protected]",
"name": ""
},
"to": [
{
"address": "[email protected]",
"name": ""
}
],
"subject": "Reminder: Your Invoice from Tiller, Inc. is Due",
"html": "<p>Hey Junia Pretious,</p><p>I just wanted to remind you that your invoice for last month's services is now due. I've attached it here for your convenience.</p><p>Thanks for your business!</p>",
"attachments": [
{
"content": "JVBERi0xLjMKJcTl8uXrp...",
"filename": "invoice.pdf",
"contentType": "application/pdf",
"encoding": "base64"
}
],
"headers": {},
"messageId": "<[email protected]>"
}

The `attachments.content` string is your encoded PDF file, so I’ve truncated it above for brevity’s sake.

To test the email with a real SMTP server, you can use Mailtrap. Assuming you have an account, replace the `createTransport({jsonTransport: true})` call with the following:

createTransport({
host: "smtp.mailtrap.io",
port: 2525,
auth: {
user: "<YOUR_MAILTRAP_USERID>",
pass: "<YOUR_MAILTRAP_PASS>"
}
})

Now, when you email the invoice, Mailtrap will capture the output and let you download the PDF attachment. In your Mailtrap account, you should see an email like the one below:

Once you’re ready to deploy your app to production, replace the Mailtrap SMTP credentials with a production mail server. Your web application can now generate and send PDF invoices to clients whenever your billing team wants to follow up.

If you need to present invoices online and send them as PDFs, the above application should give you a useful starting point. Foxit’s HTML to PDF tool is a convenient and performant way to generate PDFs, but it’s not the only solution they provide. With SDKs for a number of platforms, Foxit is the clear option when you need to build PDF support into your web, mobile, or desktop applications.

Author: Karl Hughes