paint-brush
How to Create a PDF Invoicing Web Application Using NodeJS and Foxit PDF SDKby@foxitsoftware
8,614 reads
8,614 reads

How to Create a PDF Invoicing Web Application Using NodeJS and Foxit PDF SDK

by Foxit SoftwareJune 23rd, 2023
Read on Terminal Reader
Read this story w/o Javascript

Too Long; Didn't Read

Digital invoicing has become a common practice across various industries. Web application developers often face the task of programmatically generating and sending PDF invoices. Foxit’s __PDF library provides a quick and secure solution for generating PDF files. In this tutorial, we will walk you through the process of building a web application that enables your billing department to manage unpaid invoice effectively.
featured image - How to Create a PDF Invoicing Web Application Using NodeJS and Foxit PDF SDK
Foxit Software HackerNoon profile picture


Generating payment invoices is a crucial aspect of any business, and digital invoicing has become a common practice across various industries. In this context, web application developers often face the task of programmatically generating and sending PDF invoices.


Whether you’re automating the Invoice generation and notification process or developing a graphical user Interface (GUI) to proactively remind clients about outstanding Invoices, the initial technical challenge lies in generating PDF Invoices. While you could develop a custom script for PDF generation, that would be a significant undertaking. While web-based services are convenient, they may not be suitable if you have confidentiality agreements with your clients since sending data to a third-party service over the internet can pose concerns.


Fortunately, Foxit’s PDF library provides a quick and secure solution for generating PDF files. By utilizing their HTML to PDF converter, you can convert any HTML document, including Invoices, into a PDF file that can be attached to an email or made available for clients to download from your web application.


This tutorial will guide you through the process of creating a Node.js application that leverages the Foxit PDF SDK to generate PDF Invoices from HTML Invoices within a web app. Once generated, you will utilize Nodemailer to send the Invoice via SMTP to the client’s email address. You can follow the steps outlined below or access the complete codebase on Foxit’s GitHub repository.


Building a Web Application to Create and Send PDF Invoices

In this tutorial, we will walk you through the process of building a web application that enables your billing department to manage unpaid invoices effectively. You will create various features, including an internal tool for follow-up, a page displaying outstanding invoices, and a preview page for each Invoice. Users will have the option to send email reminders to clients with the attached Invoice.


For this project, we will be utilizing the Express web framework, Pure CSS for styling, and Nodemailer for email functionality.


Prerequisites:


Creating a New Express App

To create a new boilerplate Express web application, you need to 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, you need to add the Nodemailer npm package and install Express’ dependencies:


npm i nodemailer && npm i


The default application generated by Express provides you 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 bulk of the work in this application is within the invoices router.


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


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


[
{
"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
}
]


The three invoices you can see above contain customer, plan, and billing data which will help you generate an invoice in the next section.


Creating the Invoices Routes

The routes/invoices.js file will create three new routes within 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 they send it to the client.
  • /invoices/:id/email – An endpoint that generates and sends the PDF invoice to the contact email which is on file.


Open the invoices.js file and add the following code snippet to define the first two routes:


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 nearly ready to test, but you need to create the two view files first.


Adding Views and Styles

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


Also, 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>


To add styles to your application’s stylesheet and incorporate the Pure CSS module for a visually appealing look, please open the views/layout.hbs file and replace its contents with the following code snippet. This will 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’spublic/style.css file and add the following CSS code snippet:


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 it is not mandatory, adding styles to your invoices can enhance their professional appearance, as Foxit captures all the styling from your HTML document when generating PDFs.

Now, you are ready to test your application. Please run npm start in your command line interface and open your web browser to localhost:3000/invoices You will be presented with a list of invoices similar to the following:


Click on “View” to preview each invoice.



In the final two steps, you will utilize the Foxit__HTML to PDF tool__ to generate PDF invoices. Once the invoices are generated, you will then proceed to attach them to an email using Nodemailer before sending them.


Generating PDFs with Foxit

Foxit’s SDK offers a range of functionalities for PDF creation and manipulation, including the ability to generate a PDF file from an HTML document or URL. The process of downloading and compiling the HTML to PDF executable is available here. Once you have successfully executed the demo from your command line, you can proceed with the next steps.


Node’s child_process library includes a function called exec() that enables you to execute command-line functions. This method is useful for running C++ executables from Foxit. To execute the HTML to PDF executable, please update your /:id/email route with the following code:


...
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 running this code, please ensure that you update the htmlToPdfPath variable to reflect the correct path to your htmltopdf executable.


To send an email reminder for an invoice, please go back to your list of invoices and click on “Email Reminder” for any specific invoice. This action will trigger the Node app to call the htmltopdf executable. The executable, in turn, will convert the invoice from the HTML document served by Express into a PDF file. You can locate the resulting PDF file in the invoices/ directory of your web application.


Now that you have the ability to generate PDF Invoices, the final step is to send these invoices to your customers.

Sending Emails with Nodemailer

Nodemailer offers a convenient interface that allows you to access various email transport layers. SMTP is one of the most commonly used options, but you can also utilize services like Amazon SES or your server’s sendmail command as alternatives.


To test Nodemailer, you can utilize the stream transport’s JSON option, which allows you to log the message to your console. To set up your message and send it using Nodemailer, add the following code snippet 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');
  }
});
...


Please refresh your Node application and click on “Email Reminder” for any of the invoices. This time, you will observe the complete email data object displayed 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]>"
}


Theattachments.content string represents the encoded PDF file. For the sake of brevity, I have truncated it in the above example.


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


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


Upon emailing the Invoice, Mailtrap will capture the output and provide you with the option to download the PDF attachment. If you access your Mailtrap account, you will find an email similar to the example below:



Once you’re ready to deploy your app to production, replace the Mailtrap SMTP credentials with a production mail server. Our web application now offers the capability to generate and automatically send PDF Invoices to clients upon request from our billing team.


If you require a solution for presenting Invoices online and sending them as PDFs, our application provides a reliable foundation. While Foxit’s HTML to PDF tool is a convenient and efficient option for generating PDFs, it’s important to note that they offer various other solutions as well. With their software development kits (SDKs) available for multiple platforms, Foxit stands out as the ideal choice when integrating PDF functionality into your web, mobile, or desktop applications.


Also published here.