Skip to content

tawseefnabi/pdf-node

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

78 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pdf-node

A powerful PDF generation library for Node.js with first-class TypeScript support

CI npm version TypeScript License: MIT

Features

  • 🚀 Generate PDFs from HTML templates with multiple template engines (Handlebars, EJS, or plain HTML)
  • ⚡ Template pre-compilation and caching for better performance
  • 📦 Works with both JavaScript and TypeScript
  • ✨ Supports both CommonJS and ES Modules
  • 🎨 Customize PDF options (format, orientation, borders, etc.)
  • 🔥 Async/await support
  • 📝 Type definitions included
  • 🛠️ CLI support

Table of Contents

Installation

# Using npm
npm install pdf-node

# Using yarn
yarn add pdf-node

# Using pnpm
pnpm add pdf-node

Template Engine Dependencies

By default, pdf-node includes support for Handlebars. To use EJS templates, you'll need to install the EJS package:

# Install EJS for EJS template support
npm install ejs @types/ejs

TypeScript Support

This package includes TypeScript type definitions. For the best experience, install these dev dependencies:

npm install --save-dev typescript @types/node @types/handlebars

Quick Start

1. Create a simple HTML template (template.html)

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>User Report</title>
		<style>
			body {
				font-family: Arial, sans-serif;
			}
			.user {
				margin-bottom: 20px;
				padding: 10px;
				border: 1px solid #eee;
			}
			.header {
				background: #f5f5f5;
				padding: 20px;
				text-align: center;
			}
		</style>
	</head>
	<body>
		<div class="header">
			<h1>User Report</h1>
			<p>Generated on {{date}}</p>
		</div>

		{{#each users}}
		<div class="user">
			<h2>{{name}}</h2>
			<p>Age: {{age}}</p>
			<p>Email: {{email}}</p>
		</div>
		{{/each}}
	</body>
</html>

2. Generate PDF (JavaScript)

// CommonJS
const {generatePDF} = require('pdf-node');
const fs = require('fs');
const path = require('path');

// Or ES Modules
// import { generatePDF } from 'pdf-node';
// import { fileURLToPath } from 'url';
// import { dirname } from 'path';
// const __filename = fileURLToPath(import.meta.url);
// const __dirname = dirname(__filename);

async function createPDF() {
	// Read HTML template
	const html = fs.readFileSync(path.join(__dirname, 'template.html'), 'utf8');

	// Sample data
	const users = [
		{name: 'John Doe', age: 30, email: 'john@example.com'},
		{name: 'Jane Smith', age: 25, email: 'jane@example.com'}
	];

	// PDF options
	const options = {
		format: 'A4',
		orientation: 'portrait',
		border: '10mm',
		header: {
			height: '15mm',
			contents:
				'<div style="text-align: center;">Confidential Report</div>'
		},
		footer: {
			height: '15mm',
			contents: {
				default:
					'<div style="text-align: center; color: #666;">Page {{page}} of {{pages}}</div>'
			}
		}
	};

	// Generate PDF
	try {
		const result = await generatePDF({
			html: html,
			data: {
				users: users,
				date: new Date().toLocaleDateString()
			},
			path: './user-report.pdf',
			type: 'pdf',
			pdfOptions: options
		});

		console.log('PDF generated successfully:', result.filename);
	} catch (error) {
		console.error('Error generating PDF:', error);
	}
}

createPDF();

TypeScript Usage

import {generatePDF, PDFOptions} from 'pdf-node';
import * as fs from 'fs';
import * as path from 'path';
import {fileURLToPath} from 'url';

// For ES Modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Define TypeScript interfaces
interface User {
	name: string;
	age: number;
	email: string;
}

interface TemplateData {
	users: User[];
	date: string;
}

async function generateUserReport() {
	// Read HTML template
	const html = fs.readFileSync(path.join(__dirname, 'template.html'), 'utf8');

	// Sample typed data
	const users: User[] = [
		{name: 'John Doe', age: 30, email: 'john@example.com'},
		{name: 'Jane Smith', age: 25, email: 'jane@example.com'}
	];

	// PDF options with TypeScript type
	const options: PDFOptions = {
		format: 'A4',
		orientation: 'portrait',
		border: '10mm',
		header: {
			height: '15mm',
			contents:
				'<div style="text-align: center;">Confidential Report</div>'
		},
		footer: {
			height: '15mm',
			contents: {
				default:
					'<div style="text-align: center; color: #666;">Page {{page}} of {{pages}}</div>'
			}
		}
	};

	// Generate PDF with buffer output
	try {
		const result = await generatePDF({
			html: html,
			data: {
				users: users,
				date: new Date().toLocaleDateString()
			},
			type: 'pdf',
			buffer: true, // Get PDF as buffer
			pdfOptions: options
		});

		// Example: Save buffer to file
		if ('buffer' in result) {
			fs.writeFileSync('./user-report-buffer.pdf', result.buffer);
			console.log('PDF generated from buffer');
		}

		// Or use the file path if not using buffer
		if ('filename' in result) {
			console.log('PDF generated at:', result.filename);
		}
	} catch (error) {
		console.error('Error generating PDF:', error);
	}
}

generateUserReport();
  • Step 2 - Create your HTML Template

    <!DOCTYPE html>
    <html lang="en"></html>
<title>Hello world!</title>

User List

    {{#each users}}
  • Name: {{this.name}}
  • Age: {{this.age}}

  • {{/each}}
```
  • Step 3 - Provide format and orientation as per your need

    "height": "10.5in", // allowed units: mm, cm, in, px

    "width": "8in", // allowed units: mm, cm, in, px

    • or -

    "format": "Letter", // allowed units: A3, A4, A5, Legal, Letter, Tabloid

    "orientation": "portrait", // portrait or landscape

    var options = {
    	format: 'A3',
    	orientation: 'portrait',
    	border: '10mm',
    	header: {
    		height: '45mm',
    		contents:
    			'<div style="text-align: center;">Author: Shyam Hajare</div>'
    	},
    	footer: {
    		height: '28mm',
    		contents: {
    			first: 'Cover page',
    			2: 'Second page', // Any page number is working. 1-based index
    			default:
    				'<span style="color: #444;">{{page}}</span>/<span>{{pages}}</span>', // fallback value
    			last: 'Last Page'
    		}
    	}
    };
  • Step 4 - Provide HTML, user data and output configuration

    var users = [
    	{
    		name: 'alpha',
    		age: '21'
    	},
    	{
    		name: 'beta',
    		age: '23'
    	},
    	{
    		name: 'gamma',
    		age: '29'
    	}
    ];
    
    // For file output (default)
    var document = {
    	html: html,
    	data: {
    		users: users
    	},
    	path: './output.pdf',
    	type: 'pdf'
    };
    
    // For buffer output (useful for APIs/web services)
    var documentBuffer = {
    	html: html,
    	data: {
    		users: users
    	},
    	type: 'pdf',
    	buffer: true // Enable buffer output
    };
  • Step 5- After setting all parameters, just pass document and options to pdf method.

    pdf.generatePDF(document, options)
    	.then(res => {
    		console.log(res);
    	})
    	.catch(error => {
    		console.error(error);
    	});
  • Step 5 (Alternative) - Generate PDF (Buffer Output)

    pdf.generatePDF(documentBuffer, options)
    	.then(res => {
    		console.log('Buffer size:', res.size, 'bytes');
    		console.log('Content type:', res.type);
    		// Use res.buffer for HTTP responses, saving to database, etc.
    	})
    	.catch(error => {
    		console.error(error);
    	});

Template Guide

Supported Template Engines

pdf-node supports multiple template engines out of the box:

1. Handlebars (Default)

Handlebars is the default template engine. It provides a simple syntax for inserting data into templates.

<h1>{{title}}</h1>
<ul>
	{{#each items}}
		<li>{{this.name}} - ${{this.price}}</li>
	{{/each}}
</ul>

2. EJS

EJS allows you to embed JavaScript directly in your templates.

<h1><%= title %></h1>
<ul>
  <% items.forEach(function(item) { %>
    <li><%= item.name %> - $<%= item.price.toFixed(2) %></li>
  <% }); %>
</ul>

3. Plain HTML

For simple use cases, you can use plain HTML with basic variable substitution:

<h1>Static Report</h1>
<p>This is a static HTML template that will be converted to PDF.</p>

Template Caching

For better performance, templates are compiled and cached by default. You can control this behavior using the cacheTemplate option:

// Disable caching for this template
const result = await generatePDF({
	html: template,
	data: {
		/* ... */
	},
	engine: 'handlebars', // or 'ejs' or 'html'
	cacheTemplate: false // Disable caching
	// ...other options
});

// Clear the template cache if needed
import {clearTemplateCache} from 'pdf-node';
clearTemplateCache();

Template Data

You can pass any JavaScript object as template data. The data will be available in your templates according to the template engine's syntax.

const data = {
	title: 'Sales Report',
	date: new Date().toLocaleDateString(),
	items: [
		{name: 'Product A', price: 19.99},
		{name: 'Product B', price: 29.99}
	],
	total: 49.98
};

Buffer Output for APIs and Web Services

The buffer output feature is perfect for web APIs and services where you need to return PDF data directly to clients without saving temporary files:

// Express.js API example
const express = require('express');
const pdf = require('pdf-node');
const app = express();

app.get('/generate-report/:userId', async (req, res) => {
	try {
		// Your HTML template
		const html = `
      <h1>User Report</h1>
      <p>Report for user: {{user.name}}</p>
      <p>Generated on: {{date}}</p>
    `;

		// Document configuration for buffer output
		const document = {
			html: html,
			data: {
				user: {name: 'John Doe'},
				date: new Date().toLocaleDateString()
			},
			type: 'pdf',
			buffer: true // Enable buffer output
		};

		// Generate PDF buffer
		const result = await pdf.generatePDF(document);

		// Set appropriate headers and send buffer
		res.setHeader('Content-Type', 'application/pdf');
		res.setHeader(
			'Content-Disposition',
			'attachment; filename="report.pdf"'
		);
		res.setHeader('Content-Length', result.size);
		res.send(result.buffer);
	} catch (error) {
		res.status(500).json({error: 'Failed to generate PDF'});
	}
});

Output Options

File Output (Default)

const document = {
	html: htmlTemplate,
	data: templateData,
	type: 'pdf',
	path: './output.pdf' // Required for file output
};

// Returns: { filename: '/path/to/output.pdf' }

Buffer Output (New Feature)

const document = {
	html: htmlTemplate,
	data: templateData,
	type: 'pdf',
	buffer: true // Enable buffer output
};

// Returns: { buffer: Buffer, size: number, type: 'application/pdf' }

Manual Page Breaks

You can manually insert a page break in your PDF using the addNewPage function:

var pdf = require('pdf-node');

// Add a page break in your HTML template
var html = `
  <h1>Page 1 Content</h1>
  <p>This is on the first page.</p>
  
  ${pdf.addNewPage()}
  
  <h1>Page 2 Content</h1>
  <p>This is on the second page.</p>
`;

Advanced Usage Examples

Database Integration with Buffer Output

const pdf = require('pdf-node');
const fs = require('fs');

// Generate invoice PDF and save to database
async function generateInvoiceBuffer(invoiceData) {
	const htmlTemplate = fs.readFileSync('./templates/invoice.html', 'utf8');

	const document = {
		html: htmlTemplate,
		data: invoiceData,
		type: 'pdf',
		buffer: true
	};

	const result = await pdf.generatePDF(document);

	// Save buffer to database (example with MongoDB)
	await db.collection('invoices').insertOne({
		invoiceId: invoiceData.id,
		pdfData: result.buffer,
		size: result.size,
		createdAt: new Date()
	});

	return result;
}

REST API with Different Response Types

const express = require('express');
const pdf = require('pdf-node');
const app = express();

app.get('/api/report/:format', async (req, res) => {
	const {format} = req.params; // 'download', 'inline', 'base64'

	const document = {
		html: reportTemplate,
		data: reportData,
		type: 'pdf',
		buffer: true
	};

	try {
		const result = await pdf.generatePDF(document);

		switch (format) {
			case 'download':
				res.setHeader('Content-Type', 'application/pdf');
				res.setHeader(
					'Content-Disposition',
					'attachment; filename="report.pdf"'
				);
				res.send(result.buffer);
				break;

			case 'inline':
				res.setHeader('Content-Type', 'application/pdf');
				res.setHeader('Content-Disposition', 'inline');
				res.send(result.buffer);
				break;

			case 'base64':
				res.json({
					pdf: result.buffer.toString('base64'),
					size: result.size,
					type: result.type
				});
				break;

			default:
				res.status(400).json({error: 'Invalid format'});
		}
	} catch (error) {
		res.status(500).json({error: 'PDF generation failed'});
	}
});

Microservice Architecture Example

// PDF Generation Microservice
const express = require('express');
const pdf = require('pdf-node');
const app = express();

app.post('/generate-pdf', async (req, res) => {
	const {template, data, options = {}} = req.body;

	try {
		const document = {
			html: template,
			data: data,
			type: 'pdf',
			buffer: true
		};

		const result = await pdf.generatePDF(document);

		// Return buffer as base64 for JSON response
		res.json({
			success: true,
			pdf: result.buffer.toString('base64'),
			metadata: {
				size: result.size,
				type: result.type,
				generatedAt: new Date().toISOString()
			}
		});
	} catch (error) {
		res.status(500).json({
			success: false,
			error: error.message
		});
	}
});

Configuration Options

Document Object Properties

Property Type Required Description
html String HTML template string (supports Handlebars syntax)
data Object Data to inject into the template
type String Output type ('pdf' or 'buffer'). Default: 'pdf'
path String ⚠️ Required for file output. Absolute or relative path
buffer Boolean ⚠️ Set to true for buffer output. Cannot use with path
engine String Template engine to use ('handlebars', 'ejs', or 'html'). Default: 'handlebars'
cacheTemplate Boolean Whether to cache the compiled template. Default: true
pdfOptions Object Options for PDF generation (see html-pdf for details)

Return Values

File Output

{
	filename: '/absolute/path/to/output.pdf';
}

Buffer Output

{
  buffer: Buffer,      // PDF data as Node.js Buffer
  size: 25600,        // Size in bytes
  type: 'application/pdf'  // MIME type
}

Error Handling

try {
	const result = await pdf.generatePDF(document);
	console.log('PDF generated successfully');
} catch (error) {
	if (error.message.includes('options are missing')) {
		console.error('Invalid document configuration');
	} else if (error.message.includes('Path is required')) {
		console.error('Path required when buffer is not enabled');
	} else if (error === 'only pdf file type supported') {
		console.error('Invalid document type');
	} else {
		console.error('PDF generation failed:', error.message);
	}
}

Best Practices

Memory Management

  • Buffer output is ideal for small to medium PDFs (< 50MB)
  • File output is better for large PDFs to avoid memory issues
  • Always handle buffers promptly to prevent memory leaks

Performance Tips

  • Cache compiled Handlebars templates for repeated use
  • Use streaming for large file downloads
  • Consider PDF compression for smaller file sizes

Security Considerations

  • Sanitize user input in templates to prevent XSS
  • Validate file paths to prevent directory traversal
  • Implement rate limiting for PDF generation endpoints

Reference

Please refer to the following if you want to use conditions in your HTML template:

Handlebars


Connect

GitHub @tawseefnabi (follow) To stay up to date on free & open-source software

Twitter @NabiTowseef (follow) To get tech updates/small>

LinkedIn @TawseefAhmad (connect) On the LinkedIn profile y'all


About

A JavaScript PDF generation library for NodeJs

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 8