Skip to Content
Technical Articles
Author's profile photo Roger Cheng

Microservices – Generating PDF with Node.js & PDFKit



Portable Document Format (PDF) is one of the most common document formats used for electronic documents in businesses due to its nature of being read-only, having rich formatting styles, and being compact in size, etc. Often enough, a business application needs to generate PDF documents based on business data and images (such as receipts, certificates, etc.). This blog post shows examples of how to generate a PDF document in business applications using Node.js.

SAP Business Technology Platform (BTP) provides a fast and easy way to create, run, manage, and scale business applications in the cloud. A business application in BTP typically includes a user interface, microservices that contain business logic, and technical operations. There are also backend systems such as Enterprise Resource Planning (ERP), Supply Chain Management, Blockchain network etc. that store the business data.



Typical Business Application


The JavaScript PDF document generation library for Node.js used in this example is PDFKit ( The documentation can be found at the website (

Other libraries used in this example are Request ( and axios ( During implementation, you only need either Request or axios for requests/responses. However, for demonstration purpose, this article shows how these two libraries can be used to load an image from a web URL and display it in the PDF document.

The sample code shown in this article is partly based on another article Generating a PDF in Nodejs (


Sample Code Using PDFKit Library


From the command line or terminal, add the dependency libraries after project initialization.

npm init
npm install pdfkit
npm install request
npm install axios


Create a Node module (RequestUtil.js) to handle requests. The doRequest function returns a Promise object which eventually will return a resolved state or a rejected state. In the sample code of this article, the doRequest function is called to get the content of an image file from a web URL.

"use strict";
const _REQUEST = require("request");

module.exports =
    doRequest: doRequest

async function doRequest(requestPayload)
    return new Promise(function(resolve, reject)
        _REQUEST(requestPayload, function(err, response)
            if (err)


Create the main function (GenInvoice.js) to provide the sample invoice data to be displayed in the generated PDF document. The sample invoice data is defined as a JSON object. It is passed as an argument into InvoiceGenerator.js to generate the PDF document.

"use strict"
const _INVOICE_GENERATOR = require("./InvoiceGenerator");

const _kINVOICE_DATA =
    invoiceNumber: "9900001",
    dueDate: "March 31, 2021",
    subtotal: 8000,
    paid: 500,
    memo: "Delivery expected in 7 days",
            name: "Santa Claus",
            address: "1 Elf Street",
            city: "Arctic City",
            state: "Arctic Circle",
            postalCode: "H0H 0H0",
            country: "North Pole"
            itemCode: "12341",
            itemDescription: "Best Run laptop computer",
            quantity: 4,
            price: 2985.00
            itemCode: "12342",
            itemDescription: "Best Run desktop computer",
            quantity: 2,
            price: 2295.00


void async function main()
    await ig.generate();


Create a class (InvoiceGenerator.js) for calling the PDFKit library to generate the PDF document.

"use strict"
const _AXIOS = require("axios");
const _FS = require("fs");
const _PDFKIT = require("pdfkit");
const _REQUEST = require("request");
const _REQUEST_UTIL = require("./RequestUtil");

class InvoiceGenerator
        this.invoice = invoice;

    async generate()
        let pdfkit = new _PDFKIT();
        let pdfOutputFile = `./Invoice-${this.invoice.invoiceNumber}.pdf`;
    //  [COMMENT] The PDF file is to be written to the file system.
        await this.writeContent(pdfkit);

    async writeContent(pdfkit)
        await this.displayImage(pdfkit);

    generateHeaders(pdfkit) {...}
    generateTable(pdfkit) {...}
    drawBoxes(pdfkit) {...}
    async displayImage(pdfkit) {...}
//  displayImageBase64(pdfkit) {...}
    generateBulletList(pdfkit) {...}
    generateNumberedList(pdfkit) {...}
    generateLetteredList(pdfkit) {...}
    generateMultiLevelList(pdfkit) {...}
    generateHyperlink(pdfkit) {...}
    generateFooter(pdfkit) {...}

module.exports = InvoiceGenerator;


Function generateHeaders()

This function generates the header section of the PDF document.

    let billingAddress = this.invoice.addresses.billing;
    pdfkit.image("./SAP.png", 25, 25, {width: 150})
          .text("INVOICE", 400, 25, {align: "right"})
          .text(`Invoice Number: ${this.invoice.invoiceNumber}`, {align: "right"})
          .text(`Due Date: ${this.invoice.dueDate}`, {align: "right"})
          .text(`Balance Due: €${this.invoice.subtotal - this.invoice.paid}`, {align: "right"});
//  [COMMENT] A blank line between Balance Due and Billing Address.
    pdfkit.text(`Billing Address:\n${}`, {align: "right"})
          .text(`${billingAddress.address}\n${}`, {align: "right"})
          .text(`${billingAddress.state} ${billingAddress.postalCode}`, {align: "right"})
          .text(`${}`, {align: "right"});
    const _kPAGE_BEGIN = 25;
    const _kPAGE_END = 580;
//  [COMMENT] Draw a horizontal line.
    pdfkit.moveTo(_kPAGE_BEGIN, 200)
          .lineTo(_kPAGE_END, 200)
    pdfkit.text(`Memo: ${this.invoice.memo}`, 50, 210);
    pdfkit.moveTo(_kPAGE_BEGIN, 250)
          .lineTo(_kPAGE_END, 250)



The Generated Header


Function generateTable()

This function generates the table that contains a list of items.

    const _kTABLE_TOP_Y = 270;
    const _kITEM_CODE_X = 50;
    const _kDESCRIPTION_X = 100;
    const _kQUANTITY_X = 250;
    const _kPRICE_X = 300;
    const _kAMOUNT_X = 350;
          .text("Item Code", _kITEM_CODE_X, _kTABLE_TOP_Y, {bold: true, underline: true})
          .text("Description", _kDESCRIPTION_X, _kTABLE_TOP_Y, {bold: true, underline: true})
          .text("Quantity", _kQUANTITY_X, _kTABLE_TOP_Y, {bold: true, underline: true})
          .text("Price", _kPRICE_X, _kTABLE_TOP_Y, {bold: true, underline: true})
          .text("Amount", _kAMOUNT_X, _kTABLE_TOP_Y, {bold: true, underline: true});
    let items = this.invoice.items;
    for (let idx = 0; idx < items.length; idx++)
        let item = items[idx];
        let yCoord = _kTABLE_TOP_Y + 25 + (idx * 25);
              .text(`${item.itemCode}`, _kITEM_CODE_X, yCoord)
              .text(`${item.itemDescription}`, _kDESCRIPTION_X, yCoord)
              .text(`${item.quantity}`, _kQUANTITY_X, yCoord)
              .text(`€${item.price}`, _kPRICE_X, yCoord)
              .text(`€${item.price * item.quantity}`, _kAMOUNT_X, yCoord);



The Generated Table


Function drawBoxes()

This function draws some boxes.

    const _kTOP_H_LINE_X = 100;
    const _kTOP_H_LINE_Y = 500;
    const _kBOTTOM_H_LINE_X = 100;
    const _kBOTTOM_H_LINE_Y = 525;
    const _kH_LINE_LENGTH = 400;
//  [COMMENT] Draw the 2 horizontal lines.
    pdfkit.moveTo(_kTOP_H_LINE_X, _kTOP_H_LINE_Y)
          .lineTo(_kH_LINE_LENGTH, _kTOP_H_LINE_Y)
    pdfkit.moveTo(_kBOTTOM_H_LINE_X, _kBOTTOM_H_LINE_Y)
          .lineTo(_kH_LINE_LENGTH, _kBOTTOM_H_LINE_Y)
    const _kV_LINE_1_X = 100;
    const _kV_LINE_2_X = 200;
    const _kV_LINE_3_X = 300;
    const _kV_LINE_4_X = 400;
    const _kV_LINE_LENGTH = 25;
//  [COMMENT] Draw the vertical lines.
    pdfkit.moveTo(_kV_LINE_1_X, _kTOP_H_LINE_Y)
          .lineTo(_kV_LINE_1_X, _kTOP_H_LINE_Y + _kV_LINE_LENGTH)
    pdfkit.moveTo(_kV_LINE_2_X, _kTOP_H_LINE_Y)
          .lineTo(_kV_LINE_2_X, _kTOP_H_LINE_Y + _kV_LINE_LENGTH)
    pdfkit.moveTo(_kV_LINE_3_X, _kTOP_H_LINE_Y)
          .lineTo(_kV_LINE_3_X, _kTOP_H_LINE_Y + _kV_LINE_LENGTH)
    pdfkit.moveTo(_kV_LINE_4_X, _kTOP_H_LINE_Y)
          .lineTo(_kV_LINE_4_X, _kTOP_H_LINE_Y + _kV_LINE_LENGTH)
//  [COMMENT] Write the text “Box 1”, “Box 2”, and “Box 3” in the boxes.
          .text("Box 1", _kV_LINE_1_X + 5, _kTOP_H_LINE_Y + 5)
          .text("Box 2", _kV_LINE_2_X + 5, _kTOP_H_LINE_Y + 5)
          .text("Box 3", _kV_LINE_3_X + 5, _kTOP_H_LINE_Y + 5)



The Drawn Boxes


Function displayImage() – Using Request

This function gets an image from a web URL using the Request library and displays it in the PDF document.

async displayImage(pdfkit)
    let requestPayload =
        url: "",
        method: "GET",
        encoding: null
    let response = await _REQUEST_UTIL.doRequest(requestPayload);
    let statusCode = response.statusCode;
    let responseBody = response.body;
    let imgBinary = Buffer.from(responseBody, "binary");
//  [COMMENT] The image is encoded in base64 string.
    let imgBase64 = imgBinary.toString("base64");
    let img = Buffer.from(imgBase64, "base64");
    pdfkit.addPage().text("Image").image(img, 100, 100, {width: 400});


Function displayImage() – Using axios

This function gets an image from a web URL using the axios library and displays it in the PDF document.

async displayImage(pdfkit)
    let imageUrl = "";
    let response = await _AXIOS.request(
        method: "GET",
        url: imageUrl,
        responseEncoding: "binary"
    let responseData =;
    let imgBinary = Buffer.from(responseData, "binary");
//  [COMMENT] The image is encoded in base64 string.
    let imgBase64 = imgBinary.toString("base64");
    let img = Buffer.from(imgBase64, "base64");
    pdfkit.addPage().text("Image").image(img, 100, 100, {width: 400});


Function displayImageBase64() – Image Encoded in Base64 String

This function displays an image encoded in base64 string in the PDF document. An image can be read from the file system, encoded in base64 and displayed in the PDF document.

//  [COMMENT] The image is encoded in base64 string.
    let imgBase64 = "/9j/4AAQSkZJRgABAgAA ... atrdbG1sbH0LY2Nn/9k=";
    let img = Buffer.from(imgBase64, "base64");
    pdfkit.addPage().text("Image").image(img, 100, 100, {width: 400});



The Displayed Image


Function generateBulletList()

This function generates a bullet list of items.

    let theList = ["Item A", "Item B", "Item C"];
    let xCoord = 100;
    let yCoord = 400;
          .list(theList, xCoord, yCoord, {listType: "bullet"});



The Generated Bullet List


Function generateNumberedList()

This function generates a numbered list of items.

    let theList = ["Item A", "Item B", "Item C"];
    let xCoord = 100;
    let yCoord = 450;
          .list(theList, xCoord, yCoord, {listType: "numbered"});



The Generated Numbered List


Function generateLetteredList()

This function generates a lettered list of items.

    let theList = ["Item A", "Item B", "Item C"];
    let xCoord = 100;
    let yCoord = 500;
          .list(theList, xCoord, yCoord, {listType: "lettered"});



The Generated Lettered List


Function generateMultiLevelList()

This function generates a multi-level list of items.

    let theList = ["Item A", ["Item A1", "Item A2"], "Item B", "Item C"];
    let xCoord = 100;
    let yCoord = 550;
          .list(theList, xCoord, yCoord, {listType: "bullet"});



The Generated Multi-Level List


Function generateHyperlink()

This function generates a clickable hyperlink.

    let linkText = "";
    let xCoord = 100;
    let yCoord = 650;
          .text(linkText, xCoord, yCoord);
    let width = pdfkit.widthOfString(linkText);
    let height = pdfkit.currentLineHeight();
    pdfkit.underline(100, 650, width, height, {color: "blue"})
          .link(100, 650, width, height, "");

The tooltip in the figure below shows the website URL of the hyperlink.



The Generated Clickable Hyperlink


Function generateFooter()

This function generates the footer section of the PDF document.

          .text("Payment due upon receipt.", 50, 700, {align: "center"});



The Generated Footer


This is the sample of the generated PDF document.

The Generated PDF File



In this blog post, we introduced PDF generation using Node.js. There are other considerations in doing a project, such as template management, document repository management, etc. However, implementations of those properly depend on the business requirements. Whether there is a need to introduce an Enterprise Content Management (ECM) tool is beyond the scope of this blog post.



Your feedback and comments are most welcome. You may also want to check out the Q&A area of SAP BTP, Cloud Foundry Environment (


Assigned Tags

      Be the first to leave a comment
      You must be Logged on to comment or reply to a post.