Generating A PDF in Nodejs

Sharing is Caring

The PDF format is one of the most common file formats for transmitting static information. It’s a really common request for businesses to be able to export a document or data into a really well-formatted PDF file whether it be an invoice or a report.

Surveying the Options

There are two major ways to approach creating a PDF file from Nodejs. The first way is to use a PDF generation library that uses straight JavaScript/Nodejs and everything is untemplated or the other way is to use something templated in a language like HTML and then generate the pdf from it.

I normally prefer to use a template that is HTML based to create pdf files because this potentially allows marketing or business users to make some of the needed changes without necessarily requiring code changes to happen. Basically – I download the existing template and sales or marketing provides me the updates. I check the file back in and away we go!

We are going to cover both methods though because there are times I prefer to write a lot of code and do it. In this post, we will cover using a library to generate the pdf using just JavaScript.

Using a PDF Library

PDFKit is a library that I’ve used a few times for generating very simple invoices that a template wasn’t used for. Using PDFKit in an existing project is pretty easy.

From the command line / terminal we need to add it is a dependency

npm i pdfkit

After that we can use it as a module in our project by simply requiring it.

const pdfGenerator = require('pdfkit')

After that, we can start to simply use it with the core file system (commonly called fs). We then need to instantiate the library and then pipe it to the writeable stream.

const PDFGenerator = require('pdfkit')
const fs = require('fs')

// instantiate the library
let theOutput = new PDFGenerator 

// pipe to a writable stream which would save the result into the same directory
theOutput.pipe(fs.createWriteStream('TestDocument.pdf'))

// write out file
theOutput.end()

At this point, we would have a really useless pdf file that doesn’t contain anything. It looks exactly like this:

We can add text using the text method like this:

const PDFGenerator = require('pdfkit')
const fs = require('fs')

// instantiate the library
let theOutput = new PDFGenerator 

// pipe to a writable stream which would save the result into the same directory
theOutput.pipe(fs.createWriteStream('TestDocument.pdf'))

theOutput.text('Some awesome example text')

// write out file
theOutput.end()

After that we then get a pdf that would look like this:

Again, that’s still pretty bland. We would need to write quite a bit more code to format it.

The text method actually takes in an optional option property after the string that can allow us to do things like bold, underline, oblique (“italicize”), or strike.

const PDFGenerator = require('pdfkit')
const fs = require('fs')

// instantiate the library
let theOutput = new PDFGenerator 

// pipe to a writable stream which would save the result into the same directory
theOutput.pipe(fs.createWriteStream('TestDocument.pdf'))

theOutput.text('Some awesome example text', { bold: true,
    underline: true })

// write out file
theOutput.end()

That would produce a document that looks like this:

We can also do positioning if we wanted to or align the text to the centre (or “center” as Americans spell it. 🙂 ) Let’s start with aligning to the center.

const PDFGenerator = require('pdfkit')
const fs = require('fs')

// instantiate the library
let theOutput = new PDFGenerator 

// pipe to a writable stream which would save the result into the same directory
theOutput.pipe(fs.createWriteStream('TestDocument.pdf'))

theOutput.text('Some awesome example text', { bold: true,
    underline: true,
    align: 'center'
})

// write out file
theOutput.end()

We can even add in an image using the image method. When we set it we should set the “fit” size so we don’t end up potentially taking up the entire page. This is done pretty easily:

const PDFGenerator = require('pdfkit')
const fs = require('fs')

// instantiate the library
let theOutput = new PDFGenerator 

// pipe to a writable stream which would save the result into the same directory
theOutput.pipe(fs.createWriteStream('TestDocument.pdf'))

// add in a local image and set it to be 250px by 250px
theOutput.image('./door-company-logo.jpg', { fit: [250,250] })

theOutput.text('Some awesome example text', { bold: true,
    underline: true,
    align: 'center'
})

// write out file
theOutput.end()

This produces a PDF document that looks like this

And you’re now likely saying, okay Brian that’s cool but let’s do something we might actually do at work!

A Practical Example of Generating a PDF Using PDFKit

The perfect use case for generating a PDF through code is something like an invoice where you need to have a lot of control and want to do something really specific.

Let’s say that the business needs to generate a pdf based invoice from our awesome Nodejs system. Example data for our invoice looks like this:

const invoiceData = {
    addresses: {
        shipping: {
            name: 'John Doe',
            address: '2400 Fulton Street',
            city: 'San Francisco',
            state: 'CA',
            country: 'US',
            postalCode: 94118
        },
        billing: {
            name: 'John Doe',
            address: '2400 Fulton Street',
            city: 'San Francisco',
            state: 'CA',
            country: 'US',
            postalCode: 94118
        }
    },
    memo: 'As discussed',
    items: [{
            itemCode: 12341,
            description: 'Laptop Computer',
            quantity: 2,
            price: 3000,
            amount: 6000
    }, {
            itemCode: 12342,
            description: 'Printer',
            quantity: 1,
            price: 2000,
            amount: 2000
        }
    ],
    subtotal: 8000,
    paid: 0,
    invoiceNumber: 1234,
    dueDate: 'Feburary 02, 2021'
}

As you can see it’s a relatively simple data model for looping through to create a document.

To encourage reusability, we should move our logic into a new module that I’ve conveniently called called “InvoiceGenerator”. This leaves our index file really simple:

'use strict'

const InvoiceGenerator = require('./InvoiceGenerator')

const invoiceData = {
    addresses: {
        shipping: {
            name: 'John Doe',
            address: '2400 Fulton Street',
            city: 'San Francisco',
            state: 'CA',
            country: 'US',
            postalCode: 94118
        },
        billing: {
            name: 'John Doe',
            address: '2400 Fulton Street',
            city: 'San Francisco',
            state: 'CA',
            country: 'US',
            postalCode: 94118
        }
    },
    memo: 'As discussed',
    items: [{
            itemCode: 12341,
            description: 'Laptop Computer',
            quantity: 2,
            price: 3000,
            amount: 6000
    }, {
            itemCode: 12342,
            description: 'Printer',
            quantity: 1,
            price: 2000,
            amount: 2000
        }
    ],
    subtotal: 8000,
    paid: 0,
    invoiceNumber: 1234,
    dueDate: 'Feburary 02, 2021'
}

const ig = new InvoiceGenerator(invoiceData)
ig.generate()

We are only requiring our new InvoiceGenerator module that does basically all of the work.

Our InvoiceGenerator module has been split into a few methods so it looks like this:

const PDFGenerator = require('pdfkit')
const fs = require('fs')

class InvoiceGenerator {
    constructor(invoice) {
        this.invoice = invoice
    }

    generateHeaders(doc) {
        // TO DO create a pretty header...
    }

    generateTable(doc) {
        // TODO: loop through and create a new row for each item
    }

    generateFooter(doc) {
        // TODO
    }

    generate() {
        let theOutput = new PDFGenerator 

        // TODO

        // write out file
        theOutput.end()
    }
}

module.exports = InvoiceGenerator

As you can see this is starting to become a bit bigger and bigger. The completed file would look something like this:

const PDFGenerator = require('pdfkit')
const fs = require('fs')

class InvoiceGenerator {
    constructor(invoice) {
        this.invoice = invoice
    }

    generateHeaders(doc) {
        const billingAddress = this.invoice.addresses.billing

        doc
            .image('./door-company-logo.jpg', 0, 0, { width: 250})
            .fillColor('#000')
            .fontSize(20)
            .text('INVOICE', 275, 50, {align: 'right'})
            .fontSize(10)
            .text(`Invoice Number: ${this.invoice.invoiceNumber}`, {align: 'right'})
            .text(`Due: ${this.invoice.dueDate}`, {align: 'right'})
            .text(`Balance Due: $${this.invoice.subtotal - this.invoice.paid}`, {align: 'right'})
            .moveDown()
            .text(`Billing Address:\n ${billingAddress.name}\n${billingAddress.address}\n${billingAddress.city}\n${billingAddress.state},${billingAddress.country}, ${billingAddress.postalCode}`, {align: 'right'})
    
        const beginningOfPage = 50
        const endOfPage = 550

        doc.moveTo(beginningOfPage,200)
            .lineTo(endOfPage,200)
            .stroke()
                
        doc.text(`Memo: ${this.invoice.memo || 'N/A'}`, 50, 210)

        doc.moveTo(beginningOfPage,250)
            .lineTo(endOfPage,250)
            .stroke()

    }

    generateTable(doc) {
        const tableTop = 270
        const itemCodeX = 50
        const descriptionX = 100
        const quantityX = 250
        const priceX = 300
        const amountX = 350

        doc
            .fontSize(10)
            .text('Item Code', itemCodeX, tableTop, {bold: true})
            .text('Description', descriptionX, tableTop)
            .text('Quantity', quantityX, tableTop)
            .text('Price', priceX, tableTop)
            .text('Amount', amountX, tableTop)

        const items = this.invoice.items
        let i = 0


        for (i = 0; i < items.length; i++) {
            const item = items[i]
            const y = tableTop + 25 + (i * 25)

            doc
                .fontSize(10)
                .text(item.itemCode, itemCodeX, y)
                .text(item.description, descriptionX, y)
                .text(item.quantity, quantityX, y)
                .text(`$ ${item.price}`, priceX, y)
                .text(`$ ${item.amount}`, amountX, y)
        }
    }

    generateFooter(doc) {
        doc
            .fontSize(10)
            .text(`Payment due upon receipt. `, 50, 700, {
                align: 'center'
            })
    }

    generate() {
        let theOutput = new PDFGenerator 

        console.log(this.invoice)

        const fileName = `Invoice ${this.invoice.invoiceNumber}.pdf`

        // pipe to a writable stream which would save the result into the same directory
        theOutput.pipe(fs.createWriteStream(fileName))

        this.generateHeaders(theOutput)

        theOutput.moveDown()

        this.generateTable(theOutput)

        this.generateFooter(theOutput)
        

        // write out file
        theOutput.end()

    }
}

module.exports = InvoiceGenerator

This would generate an invoice that looks like this:

Formatting through code can be pretty complex because you have a lot of numbers you have to keep in mind. HTML does this out of the box because it’s markup language.

Sharing is Caring

Brian is a software architect and technology leader living in Niagara Falls with 13+ years of development experience. He is passionate about automation, business process re-engineering, and building a better tomorrow.

Brian is a proud father of four: two boys, and two girls and has been happily married to Crystal for more than ten years. From time to time, Brian may post about his faith, his family, and definitely about technology.