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.0Susan_Are
Microsoft
Joined April 05, 2023
Apps on Azure Blog
Follow this blog board to get notified when there's new activity