Blog Post

Apps on Azure Blog
9 MIN READ

PhantomJS PDF Generation on Azure Linux App Services

Susan_Are's avatar
Susan_Are
Icon for Microsoft rankMicrosoft
Jan 22, 2025

This blog aims to guide you in creating, troubleshooting and effectively executing a PhantomJS PDF Generation on Azure Linux App Services.

Local setup along with source code contents

Create a sample express application using the following commands, or for sample express application please refer this link https://expressjs.com/en/starter/hello-world.html

Please follow these instructions on your local machine to create an Express application using the PhantomJS library

- Create a New Project Directory

mkdir phanthomJS 
cd phanthomJS

- Initialize the Project: Run "npm init -y" to create a package.json file. The '-y' flag automatically sets default values.

npm init -y

- Update the "package.json" with the following contents

{
  "name": "express-pdf-generator",
  "version": "1.0.0",
  "description": "An Express application to generate PDFs from dynamic HTML templates.",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "keywords": ["express", "pdf", "dynamic-html-pdf", "nodejs"],
  "author": "Your Name",
  "license": "MIT",
  "dependencies": {
    "dynamic-html-pdf": "^1.0.4",
    "express": "^4.21.2"
  }
}

- Edit "index.js" with the following contents. This would help to set up an Express application for PDF generation using PhantomJS library

const express = require('express');
const path = require('path');
const fs = require('fs');
const pdf = require('dynamic-html-pdf');
const app = express();
const port = 3000;
 
app.get('/generate-pdf', async (req, res) => {
    // Example HTML template (you can replace this with your actual HTML)
    const htmlContent = `
<html>
<head><title>Sample PDF</title></head>
<body>
<h1>Users List</h1>
<ul>
            {{#data}}
<li>{{name}}</li>
            {{/data}}
</ul>
</body>
</html>
    `;
    // Example data (replace with your actual user data)
    const users = [
        { name: 'John Doe' },
        { name: 'Jane Smith' },
        { name: 'Alice Johnson' }
    ];
 
    // Define the options for the PDF generation (A4, portrait orientation)
    const options = {
        format: "A4",
        orientation: "portrait",
    };
 
    // Define the document structure, including the template and context
    const document = {
        type: 'buffer',  // 'file' or 'buffer' (buffer will send the result directly)
        template: htmlContent,  // The HTML template to render the PDF
        context: {
            data: users  // Users data to pass into the template
        },
        path: "./output.pdf"  // Optional: Path where the PDF will be saved
    };
 
    try {
        // Generate the PDF as a buffer
        const pdfBuffer = await pdf.create(document, options);
 
        // Save the generated PDF buffer to a file
        const outputPath = path.join(__dirname, 'output.pdf');
        fs.writeFileSync(outputPath, pdfBuffer);
 
        // Respond with the PDF file as a download
        res.download(outputPath, 'output.pdf', (err) => {
            if (err) {
                console.error('Error sending file:', err);
            } else {
                console.log('PDF sent to client successfully!');
            }
        });
    } catch (err) {
        console.error('Error generating PDF:', err);
        res.status(500).send('Error generating PDF');
    }
});
 
app.listen(port, () => {
    console.log(`Server running on http://localhost:${port}`);
});

- Execute following commands to install dependencies and run the server 

npm install 
npm start

- The application is running without any issues, and following is the expected output

 

 

 

 

 

- Following the package installation, you will observe the package-lock.json file and the node_modules folder, and the current folder structure should show as below

- Now, open your browser and access the API http://localhost:3000/generate-pdf

 

 

 

- We are able to generate pdf file locally without any issues.

Let's proceed with deploying to Azure App Service for Linux.

App Service Node.js Environment Setup

To get started in Azure, please proceed with setting up a Linux App Service using Node.js 18 or higher version. For this specific example, we will be utilizing Node.js 20 LTS. Ensure that you have the necessary permissions on the subscription to facilitate this setup. If you encounter any issues during the setup process, refer to the official documentation https://learn.microsoft.com/en-us/azure/app-service/quickstart-nodejs?tabs=linux&pivots=development-environment-azure-portal

- After provisioning the app service, please enable App Service Logs for debugging

- You may utilize any of the available deployment methods  to deploy the code to the app service.

- Since the application is configured to listen on port 3000, please add an App setting with PORT=3000 under Environment Variables.

- Upon accessing this API "https://appservicename.azurewebsites.net/generate-pdf" you will be encountering an error in generating pdf file.

 

 

 

 

- Please navigate to kudu "/newui" portal, to view the default_docker.log (Application logs)

 https://<AppServiceName>.scm.azurewebsites.net/newui/fileManager# 

2025-01-20T09:51:05.196502989Z    _____                               
2025-01-20T09:51:05.196799610Z   /  _  \ __________ _________   ____  
2025-01-20T09:51:05.196809910Z  /  /_\  \\___   /  |  \_  __ \_/ __ \ 
2025-01-20T09:51:05.196815111Z /    |    \/    /|  |  /|  | \/\  ___/ 
2025-01-20T09:51:05.196819411Z \____|__  /_____ \____/ |__|    \___  >
2025-01-20T09:51:05.196824211Z         \/      \/                  \/ 
2025-01-20T09:51:05.196829112Z A P P   S E R V I C E   O N   L I N U X
2025-01-20T09:51:05.196833712Z 
2025-01-20T09:51:05.196838312Z Documentation: http://aka.ms/webapp-linux
2025-01-20T09:51:05.196842913Z NodeJS quickstart: https://aka.ms/node-qs
2025-01-20T09:51:05.196847613Z NodeJS Version : v20.15.1
2025-01-20T09:51:05.196852113Z Note: Any data outside '/home' is not persisted
2025-01-20T09:51:05.196856814Z 
2025-01-20T09:51:06.463155052Z Starting OpenBSD Secure Shell server: sshd.
2025-01-20T09:51:06.475714842Z WEBSITES_INCLUDE_CLOUD_CERTS is not set to true.
2025-01-20T09:51:06.534554812Z Starting periodic command scheduler: cron.
2025-01-20T09:51:06.619867957Z Could not find build manifest file at '/home/site/wwwroot/oryx-manifest.toml'
2025-01-20T09:51:06.619970764Z Could not find operation ID in manifest. Generating an operation id...
2025-01-20T09:51:06.620649212Z Build Operation ID: 0c834bdc-1dec-4f5f-b587-361501aa219e
2025-01-20T09:51:06.738420658Z Environment Variables for Application Insight's IPA Codeless Configuration exists..
2025-01-20T09:51:06.741196654Z Writing output script to '/opt/startup/startup.sh'
2025-01-20T09:51:06.750827537Z Running #!/bin/sh
2025-01-20T09:51:06.750849338Z 
2025-01-20T09:51:06.750856039Z # Enter the source directory to make sure the script runs where the user expects
2025-01-20T09:51:06.750861539Z cd "/home/site/wwwroot"
2025-01-20T09:51:06.750866839Z 
2025-01-20T09:51:06.750918843Z export NODE_PATH=/usr/local/lib/node_modules:$NODE_PATH
2025-01-20T09:51:06.750924744Z if [ -z "$PORT" ]; then
2025-01-20T09:51:06.750930144Z 		export PORT=8080
2025-01-20T09:51:06.750935544Z fi
2025-01-20T09:51:06.750940645Z 
2025-01-20T09:51:06.750945745Z npm start
2025-01-20T09:51:08.374110801Z npm info using npm@10.7.0
2025-01-20T09:51:08.375006866Z npm info using node@v20.15.1
2025-01-20T09:51:09.168578521Z 
2025-01-20T09:51:09.168619324Z > express-pdf-generator@1.0.0 start
2025-01-20T09:51:09.168626425Z > node index.js
2025-01-20T09:51:09.168631825Z 
2025-01-20T09:51:10.868109687Z html-pdf: Failed to load PhantomJS module. Error: Cannot find module 'phantomjs-prebuilt'
2025-01-20T09:51:10.868160791Z Require stack:
2025-01-20T09:51:10.868167491Z - /home/site/wwwroot/node_modules/html-pdf/lib/pdf.js
2025-01-20T09:51:10.868172892Z - /home/site/wwwroot/node_modules/html-pdf/lib/index.js
2025-01-20T09:51:10.868177892Z - /home/site/wwwroot/node_modules/dynamic-html-pdf/index.js
2025-01-20T09:51:10.868183493Z - /home/site/wwwroot/index.js
2025-01-20T09:51:10.868188793Z     at Module._resolveFilename (node:internal/modules/cjs/loader:1145:15)
2025-01-20T09:51:10.868194093Z     at Module._load (node:internal/modules/cjs/loader:986:27)
2025-01-20T09:51:10.868199394Z     at Module.require (node:internal/modules/cjs/loader:1233:19)
2025-01-20T09:51:10.868204594Z     at Module.patchedRequire [as require] (/agents/nodejs/node_modules/diagnostic-channel/dist/src/patchRequire.js:16:46)
2025-01-20T09:51:10.868209895Z     at require (node:internal/modules/helpers:179:18)
2025-01-20T09:51:10.868215195Z     at Object.<anonymous> (/home/site/wwwroot/node_modules/html-pdf/lib/pdf.js:7:19)
2025-01-20T09:51:10.868220995Z     at Module._compile (node:internal/modules/cjs/loader:1358:14)
2025-01-20T09:51:10.868226196Z     at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)
2025-01-20T09:51:10.868231196Z     at Module.load (node:internal/modules/cjs/loader:1208:32)
2025-01-20T09:51:10.868236196Z     at Module._load (node:internal/modules/cjs/loader:1024:12) {
2025-01-20T09:51:10.868241297Z   code: 'MODULE_NOT_FOUND',
2025-01-20T09:51:10.868246197Z   requireStack: [
2025-01-20T09:51:10.868251298Z     '/home/site/wwwroot/node_modules/html-pdf/lib/pdf.js',
2025-01-20T09:51:10.868256598Z     '/home/site/wwwroot/node_modules/html-pdf/lib/index.js',
2025-01-20T09:51:10.868261398Z     '/home/site/wwwroot/node_modules/dynamic-html-pdf/index.js',
2025-01-20T09:51:10.868266199Z     '/home/site/wwwroot/index.js'
2025-01-20T09:51:10.868270799Z   ]
2025-01-20T09:51:10.868275699Z }
2025-01-20T09:51:10.884502480Z Server running on http://localhost:3000

2025-01-20T09:51:39.369462936Z Error generating PDF: AssertionError [ERR_ASSERTION]: html-pdf: Failed to load PhantomJS module. You have to set the path to the PhantomJS binary using 'options.phantomPath'
2025-01-20T09:51:39.369543943Z     at new PDF (/home/site/wwwroot/node_modules/html-pdf/lib/pdf.js:40:3)
2025-01-20T09:51:39.369551843Z     at Object.createPdf [as create] (/home/site/wwwroot/node_modules/html-pdf/lib/index.js:10:14)
2025-01-20T09:51:39.369557744Z     at /home/site/wwwroot/node_modules/dynamic-html-pdf/index.js:36:30
2025-01-20T09:51:39.369563444Z     at new Promise (<anonymous>)
2025-01-20T09:51:39.369579946Z     at module.exports.create (/home/site/wwwroot/node_modules/dynamic-html-pdf/index.js:25:12)
2025-01-20T09:51:39.369586346Z     at /home/site/wwwroot/index.js:48:37
2025-01-20T09:51:39.369591947Z     at Layer.handle [as handle_request] (/home/site/wwwroot/node_modules/express/lib/router/layer.js:95:5)
2025-01-20T09:51:39.369608948Z     at next (/home/site/wwwroot/node_modules/express/lib/router/route.js:149:13)
2025-01-20T09:51:39.369614948Z     at Route.dispatch (/home/site/wwwroot/node_modules/express/lib/router/route.js:119:3)
2025-01-20T09:51:39.369620549Z     at Layer.handle [as handle_request] (/home/site/wwwroot/node_modules/express/lib/router/layer.js:95:5) {
2025-01-20T09:51:39.369626249Z   generatedMessage: false,
2025-01-20T09:51:39.369631650Z   code: 'ERR_ASSERTION',
2025-01-20T09:51:39.369636950Z   actual: undefined,
2025-01-20T09:51:39.369642151Z   expected: true,
2025-01-20T09:51:39.369647351Z   operator: '=='
2025-01-20T09:51:39.369652852Z }

Basically, application failing with dependency error
Error generating PDF: AssertionError [ERR_ASSERTION]: html-pdf: Failed to load PhantomJS module. You have to set the path to the PhantomJS binary using 'options.phantomPath'

- To resolve this error, we need to install the dependency html-pdf globally and then create a symbolic link between the globally installed html-pdf package and the current project's node_modules folder.

The following startup.sh file will help to achieve the same

##!/bin/sh 
cd /home/site/wwwroot 
apt-get update 
apt-get -y install libfontconfig1 
npm install html-pdf -g 
npm install dynamic-html-pdf 
npm link html-pdf 
apt-get install bzip2 
npm link phantomjs-prebuilt 
npm start

- Now, place the startup.sh file with above contents in "/home" and update the Startup Command as "/home/startup.sh" as shown below

The app service will now undergo an automatic restart due to the update in the startup command.

- Test the setup by calling the API endpoint of the app service https://appservicename.azurewebsites.net/generate-pdf, and you will see that it generates a PDF file.

**Deprecation Notice**

- Be aware that PhantomJS uses some deprecated versions. Consider upgrading to the latest versions or alternative tools like puppeteer

npm warn deprecated har-validator@5.1.5: this library is no longer supported npm warn deprecated phantomjs-prebuilt@2.1.16: this package is now deprecated npm warn deprecated uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. npm warn deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142 npm info run phantomjs-prebuilt@2.1.16 install node_modules/phantomjs-prebuilt node install.js npm info run phantomjs-prebuilt@2.1.16 install { code: 0, signal: null } npm http fetch POST 200 https://registry.npmjs.org/-/npm/v1/security/advisories/bulk 110ms npm warn deprecated html-pdf@3.0.1: Please migrate your projects to a newer library like puppeteer npm info run phantomjs-prebuilt@2.1.16 install ../usr/local/lib/node_modules/phantomjs-prebuilt node install.js npm info run phantomjs-prebuilt@2.1.16 install { code: 0, signal: null }

Reference articles

https://github.com/marcbachmann/node-html-pdf/issues/677

https://github.com/marcbachmann/node-html-pdf/issues/437#issuecomment-467463285

https://github.com/projectkudu/kudu/wiki/Azure-Web-App-sandbox#unsupported-frameworks

Hope this information is helpful :)

 

 

 

 

 

 

 

 

Updated Jan 22, 2025
Version 1.0