Generate PDF Using PDFMake in Node.js
PDF generation is a common requirement in many server-side applications, from creating invoices and reports to generating statements and certificates. Node.js, with its event-driven architecture and extensive ecosystem, is perfectly suited for handling PDF generation workflows. Among the various libraries available, PDFMake stands out as a robust, flexible, and feature-rich solution for PDF generation in Node.js applications. This comprehensive guide will walk you through everything you need to know about generating PDFs using PDFMake specifically in a Node.js environment, complete with practical examples and best practices.
Why PDFMake for Node.js?
PDFMake was originally designed to work in both browser and Node.js environments, making it an excellent choice for server-side PDF generation.
Here's why PDFMake is particularly well-suited for Node.js applications:
- Pure JavaScript implementation: No native dependencies or compilation required.
- Consistent API: Same declarative approach in both client and server environments.
- Rich feature set: Supports complex layouts, tables, styling, and custom fonts.
- Active maintenance: Regular updates and strong community support.
- Performance: Efficiently handles large documents with optimized rendering.
- Integration-friendly: Works seamlessly with Express, Fastify, Nest.js, and other frameworks.
Setting Up PDFMake in Node.js
Let's start by setting up PDFMake in your Node.js application:
Installation
Install PDFMake using npm:
npm install pdfmake
Basic Setup
In a Node.js environment, you'll need to initialize PDFMake with fonts. Here's how to set up the basic structure:
// Import required modules
const PdfPrinter = require('pdfmake');
const fs = require('fs');
const path = require('path');
// Define font files
const fonts = {
Roboto: {
normal: path.join(__dirname, 'fonts/Roboto-Regular.ttf'),
bold: path.join(__dirname, 'fonts/Roboto-Medium.ttf'),
italics: path.join(__dirname, 'fonts/Roboto-Italic.ttf'),
bolditalics: path.join(__dirname, 'fonts/Roboto-MediumItalic.ttf')
}
};
// Create the printer
const printer = new PdfPrinter(fonts);
PDFMake requires at least one font to be defined. If you don't specify any fonts, you'll encounter an error when generating PDFs.
Managing Font Files
To use custom fonts in your PDFs, you need to have the font files accessible to your Node.js application. Here's a systematic approach:
- Create a
fonts
directory in your project. - Download the required font files (e.g., Roboto from Google Fonts).
- Place them in the fonts directory.
- Reference them with absolute paths as shown above.
- Bundle the fonts with your application deployment.
- Use a CDN to serve font files for distributed applications.
- Consider using environment-specific paths with configuration management.
Creating Your First PDF
Now that we have PDFMake set up, let's create a simple PDF document:
// Define the document content
const docDefinition = {
content: [
'Hello!',
'This is a simple PDF generated using PDFMake in Node.js',
{
text: 'This text is styled',
style: 'header'
},
{
text: [
'You can also ',
{ text: 'combine ', bold: true },
{ text: 'different ', italics: true },
{ text: 'styles ', bold: true, italics: true },
'in one text block'
]
}
],
styles: {
header: {
fontSize: 28,
bold: true,
margin: [0, 20, 0, 10]
}
}
};
// Generate the PDF
const pdfDoc = printer.createPdfKitDocument(docDefinition);
// Write to a file
pdfDoc.pipe(fs.createWriteStream('output.pdf'));
pdfDoc.end();
This basic example:
- Defines a document with text content and styles.
- Creates a PDFKit document using our printer.
- Pipes the output to a file stream.
- Ends the document to finalize the generation process.
Always call pdfDoc.end()
after setting up your document. Without this, the document generation process won't complete and the file will remain incomplete.
Creating PDF Documents with Express.js
In a typical Node.js web application, you'll often want to generate PDFs on-demand and serve them to users.
Here's how to integrate PDFMake with Express.js:
const express = require('express');
const PdfPrinter = require('pdfmake');
const fs = require('fs');
const path = require('path');
const app = express();
const port = 3000;
// Define font files
const fonts = {
Roboto: {
normal: path.join(__dirname, 'fonts/Roboto-Regular.ttf'),
bold: path.join(__dirname, 'fonts/Roboto-Medium.ttf'),
italics: path.join(__dirname, 'fonts/Roboto-Italic.ttf'),
bolditalics: path.join(__dirname, 'fonts/Roboto-MediumItalic.ttf')
}
};
// Create printer instance
const printer = new PdfPrinter(fonts);
// Endpoint to generate and serve a PDF
app.get('/generate-pdf', (req, res) => {
// Define the document
const docDefinition = {
content: [
{ text: 'Dynamic PDF Document', style: 'header' },
{ text: `Generated on: ${new Date().toLocaleString()}`, style: 'subheader' },
{ text: 'This PDF was generated on-demand by an Express.js server using PDFMake.' }
],
styles: {
header: {
fontSize: 22,
bold: true,
margin: [0, 0, 0, 10]
},
subheader: {
fontSize: 16,
italic: true,
margin: [0, 10, 0, 20]
}
}
};
// Create the PDF document
const pdfDoc = printer.createPdfKitDocument(docDefinition);
// Set response headers
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', 'attachment; filename=document.pdf');
// Pipe the PDF document to the response
pdfDoc.pipe(res);
pdfDoc.end();
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
This Express.js example:
- Sets up a route to handle PDF generation requests.
- Creates a document definition with dynamic content.
- Sets appropriate response headers for PDF delivery.
- Streams the generated PDF directly to the client.
When generating PDFs based on user input, always validate and sanitize this input to prevent security vulnerabilities or unexpected behavior.
Working with Document Structure
PDFMake uses a declarative approach for defining documents. Let's explore the key components and how to use them effectively in Node.js:
Document Definition
The document definition object is the main configuration for your PDF:
const docDefinition = {
// Document metadata
info: {
title: 'Sample Document',
author: 'Node.js Server',
subject: 'PDFMake Sample',
keywords: 'pdf, node.js, pdfmake',
},
// Content elements
content: [],
// Styling configuration
styles: {},
defaultStyle: {},
// Page settings
pageSize: 'A4', // 'A4', 'LETTER', or {width: number, height: number}
pageOrientation: 'portrait', // 'portrait' or 'landscape'
pageMargins: [40, 60, 40, 60], // [left, top, right, bottom]
// Headers and footers
header: {},
footer: {},
// Background definition
background: {}
};
Content Elements
PDFMake supports various content elements that can be nested and combined to create sophisticated layouts:
Text
// Simple text
{ text: 'Plain text' }
// Styled text
{
text: 'Styled text',
bold: true,
fontSize: 16,
color: 'blue',
alignment: 'center', // 'left', 'center', 'right', 'justify'
margin: [5, 2, 10, 20], // [left, top, right, bottom]
lineHeight: 1.5
}
// Text with custom styling
{
text: 'Text with custom style',
style: 'myCustomStyle'
}
// Combination of styles in one text block
{
text: [
'Regular text, ',
{ text: 'bold text, ', bold: true },
{ text: 'regular again' }
]
}
Columns
Columns are powerful for creating complex layouts with multiple content blocks side by side:
{
columns: [
{
// Column 1 - auto width
width: 'auto',
text: 'First column with automatic width'
},
{
// Column 2 - star notation for remaining space
width: '*', // Star notation means "fill the remaining space"
text: 'Second column taking the remaining width'
},
{
// Column 3 - percentage based width
width: '20%',
text: 'Third column with 20% width'
},
{
// Column 4 - fixed width
width: 100, // Fixed width of 100 units
text: 'Fourth column with fixed width'
}
],
// Optional: Gap between columns in points
columnGap: 10
}
You can nest any content inside columns, including other columns, tables, or images:
{
columns: [
{
width: '50%',
text: 'First column'
},
{
width: '50%',
// Nested column with a table
table: {
body: [
['Nested', 'Table'],
['Inside', 'Column']
]
}
}
]
}
Tables
Tables are essential for displaying structured data:
{
table: {
// Header row definition
headerRows: 1,
// Column widths - can be fixed, auto, star notation, or percentage
widths: ['*', 'auto', 100, '20%'],
// Table body
body: [
// Header row
[
{ text: 'Header 1', style: 'tableHeader' },
{ text: 'Header 2', style: 'tableHeader' },
{ text: 'Header 3', style: 'tableHeader' },
{ text: 'Header 4', style: 'tableHeader' }
],
// Data rows
['Value 1', 'Value 2', 'Value 3', 'Value 4'],
['Value 5', 'Value 6', 'Value 7', 'Value 8']
]
},
// Custom layout for the table
layout: {
hLineWidth: function(i, node) {
// Different line widths based on position
return (i === 0 || i === node.table.body.length) ? 2 : 1;
},
vLineWidth: function(i) {
return 1;
},
hLineColor: function(i) {
// Different line colors based on position
return i === 0 ? '#000' : '#aaa';
},
fillColor: function(rowIndex) {
// Zebra striping
return (rowIndex % 2 === 0) ? '#f8f8f8' : null;
},
// Padding settings
paddingLeft: function(i) { return 8; },
paddingRight: function(i) { return 8; },
paddingTop: function(i) { return 6; },
paddingBottom: function(i) { return 6; }
}
}
For convenience, PDFMake also provides predefined layouts:
// Using predefined layouts
{
table: {
// Table definition as above
},
// Use a predefined layout
layout: 'lightHorizontalLines' // Other options: 'noBorders', 'headerLineOnly', 'lightHorizontalLines'
}
Lists
PDFMake supports both ordered and unordered lists:
// Unordered list
{
ul: [
'Simple item',
{
text: 'Item with custom styling',
bold: true
},
{
// Nested list
ul: [
'Nested item 1',
'Nested item 2'
]
}
],
// Optional list styling
style: 'listStyle'
}
// Ordered list
{
ol: [
'First item',
'Second item',
{
// Nested ordered list
ol: [
'Nested ordered item 1',
'Nested ordered item 2'
]
}
],
// Custom start number for ordered list
start: 5,
// Use different marker pattern (for ordered list)
markerPattern: '(%s)',
// Counter reset
separator: ['(', ')'],
// Counter type (decimal, lowerRoman, upperRoman, lowerAlpha, upperAlpha)
type: 'lowerRoman'
}
Images
In Node.js, you have several options for including images:
// Using file paths
{
image: path.join(__dirname, 'images/logo.png'),
width: 200,
height: 100, // optional, automatically calculated from width if maintaining aspect ratio
alignment: 'center', // 'left', 'center', 'right'
margin: [0, 10, 0, 10], // [left, top, right, bottom]
fit: [250, 300] // Scale the image to fit within these dimensions while maintaining aspect ratio
}
// Using base64 data
const imageData = fs.readFileSync(path.join(__dirname, 'images/logo.png'));
const imageBase64 = Buffer.from(imageData).toString('base64');
{
image: `data:image/png;base64,${imageBase64}`,
width: 200
}
When working with many images or larger PDFs:
- Resize and compress images before adding them to the PDF.
- Use appropriate image formats.
- Consider caching base64 encoded images if they're used repeatedly.
Advanced Features in Node.js
Watermarks
Adding watermarks is a common requirement for many PDF documents. PDFMake makes it easy to add text or image watermarks using the background
property:
// Text watermark example using dedicated watermark property
const docDefinition = {
content: [
{ text: 'Document Title', style: 'header' },
{ text: 'This document contains sensitive information.' }
// More content...
],
// Dedicated watermark property for all pages
watermark: {
text: 'CONFIDENTIAL',
color: 'red',
opacity: 0.3,
fontSize: 80,
bold: true,
italics: false
},
styles: {
header: {
fontSize: 22,
bold: true,
margin: [0, 0, 0, 10]
}
}
};
For image watermarks:
// Image watermark example
const docDefinition = {
content: [
{ text: 'Document with Image Watermark', style: 'header' },
'This document features an image watermark in the background.',
// More content...
],
// Image watermark
background: function(currentPage, pageSize) {
return {
image: path.join(__dirname, 'images/watermark.png'),
width: 300,
opacity: 0.1,
absolutePosition: {
x: (pageSize.width - 300) / 2,
y: (pageSize.height - 300) / 2
}
};
},
styles: {
header: {
fontSize: 22,
bold: true,
margin: [0, 0, 0, 10]
}
}
};
Headers and Footers
Headers and footers are particularly useful for multi-page documents:
const docDefinition = {
content: [
// Multiple pages of content...
],
header: {
columns: [
{
text: 'Company Name',
alignment: 'left',
margin: [20, 10, 0, 0]
},
{
image: path.join(__dirname, 'images/logo.png'),
width: 50,
alignment: 'right',
margin: [0, 10, 20, 0]
}
]
},
footer: function (currentPage, pageCount) {
return {
text: `Page ${currentPage} of ${pageCount}`,
alignment: 'center',
margin: [0, 10, 0, 0]
};
}
};
Table of Contents
For longer documents, a table of contents can be automatically generated:
const docDefinition = {
content: [
{ text: 'Document Title', style: 'title' },
// Table of contents
{
toc: {
title: { text: 'Table of Contents', style: 'tocTitle' },
// Optional: Create dot leaders between text and page numbers
textMargin: [0, 0, 0, 0],
textStyle: { fontSize: 12 },
numberStyle: { fontSize: 12 }
}
},
// Content with toc items
{ text: 'Chapter 1: Introduction', style: 'header', tocItem: true },
// Chapter 1 content...
{ text: 'Chapter 2: Methodology', style: 'header', tocItem: true },
// Chapter 2 content...
{ text: 'Chapter 3: Results', style: 'header', tocItem: true },
// Chapter 3 content...
{ text: 'Chapter 4: Discussion', style: 'header', tocItem: true },
// Chapter 4 content...
],
styles: {
title: { fontSize: 24, bold: true, margin: [0, 0, 0, 20] },
tocTitle: { fontSize: 20, bold: true, margin: [0, 20, 0, 20] },
header: { fontSize: 18, bold: true, margin: [0, 20, 0, 10] }
}
};
You can also manually create a table of contents with page references:
const docDefinition = {
content: [
{ text: 'Document Title', style: 'title' },
{ text: 'Table of Contents', style: 'tocTitle' },
{
table: {
widths: ['*', 'auto'],
body: [
[
{ text: 'Chapter 1: Introduction', linkToDestination: 'chapter1' },
{ pageReference: 'chapter1', alignment: 'right' }
],
[
{ text: 'Chapter 2: Methodology', linkToDestination: 'chapter2' },
{ pageReference: 'chapter2', alignment: 'right' }
]
]
},
layout: 'noBorders'
},
{ text: 'Chapter 1: Introduction', style: 'header', id: 'chapter1' },
// Chapter 1 content...
{ text: 'Chapter 2: Methodology', style: 'header', id: 'chapter2' },
// Chapter 2 content...
]
};
Document Security
You can secure PDFs with passwords in Node.js:
const docDefinition = {
content: ['Secure document content...'],
// Document security settings
userPassword: 'user-password', // Password required to open the document
ownerPassword: 'owner-password', // Password required for full access
permissions: {
printing: 'highResolution', // 'lowResolution' or 'highResolution'
modifying: false,
copying: false,
annotating: false,
fillingForms: true,
contentAccessibility: true,
documentAssembly: false
}
};
PDF Invoice Example
Here's a complete example of generating an invoice with PDFMake in Node.js:
Complete Invoice Generation Code Example
const PdfPrinter = require('pdfmake');
const fs = require('fs');
const path = require('path');
// Define fonts
const fonts = {
Roboto: {
normal: path.join(__dirname, 'fonts/Roboto-Regular.ttf'),
bold: path.join(__dirname, 'fonts/Roboto-Medium.ttf'),
italics: path.join(__dirname, 'fonts/Roboto-Italic.ttf'),
bolditalics: path.join(__dirname, 'fonts/Roboto-MediumItalic.ttf')
}
};
// Create the printer
const printer = new PdfPrinter(fonts);
// Define brand colors
const colors = {
primary: '#0c63af',
background: '#f1f1f1',
text: '#1e293b',
border: '#e2e8f0',
lightText: '#718096'
};
// Sample invoice data (normally from a database)
const invoiceData = {
invoice_number: 'INV-001',
date: new Date().toLocaleDateString(),
due_date: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toLocaleDateString(),
company: {
name: 'ByteCraft Code',
address: '404 Not Found Lane',
city: 'Silicon Valley, CA 94024',
phone: '(555) 123-4567',
email: '[email protected]',
website: 'www.byte.example'
},
customer: {
name: 'Pixel Perfect Designs',
address: '42 Answer Avenue',
city: 'Responsive City, NY 10101',
contact_person: 'Clippy McHelpful',
email: '[email protected]'
},
items: [
{description: 'Debugging Services', quantity: 40, unit_price: 120, tax_rate: 0.10},
{description: 'Cloud Hosting', quantity: 1, unit_price: 300, tax_rate: 0.10},
{description: 'Domain Registration', quantity: 2, unit_price: 25, tax_rate: 0.10}
],
notes: 'Thank you for your business!'
};
// Calculate invoice totals
function calculateInvoice(invoice) {
let subtotal = 0;
let totalTax = 0;
invoice.items.forEach(item => {
const amount = item.quantity * item.unit_price;
const tax = amount * item.tax_rate;
item.amount = amount;
item.tax = tax;
subtotal += amount;
totalTax += tax;
});
invoice.subtotal = subtotal;
invoice.total_tax = totalTax;
invoice.total = subtotal + totalTax;
return invoice;
}
// Process invoice data
const invoice = calculateInvoice(invoiceData);
// Format currency
function formatCurrency(value) {
return '$' + value.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,');
}
// Generate invoice PDF
function generateInvoicePDF() {
// Create PDF definition
const docDefinition = {
pageSize: 'A4',
pageMargins: [40, 50, 40, 50],
content: [
// Header section
{
columns: [
{
width: '*',
stack: [
{
text: 'INVOICE',
style: 'invoiceTitle',
alignment: 'left',
color: colors.primary,
margin: [0, 0, 0, 15]
},
{
table: {
widths: [80, 80],
headerRows: 0,
body: [
[
{text: 'Invoice', style: 'tableHeader'},
{text: invoice.invoice_number, bold: true}
],
[
{text: 'Issue Date', style: 'tableHeader'},
{text: invoice.date}
],
[
{text: 'Due Date', style: 'tableHeader'},
{text: invoice.due_date}
]
]
},
layout: {
hLineWidth: function () {
return 1;
},
vLineWidth: function () {
return 1;
},
hLineColor: function () {
return colors.border;
},
vLineColor: function () {
return colors.border;
}
}
}
]
},
{
image: path.join(__dirname, 'images/logo.png'),
width: 200,
alignment: 'right'
}
],
margin: [0, 0, 0, 30]
},
// Company & Customer Info Block
{
columns: [
{
width: 'auto',
stack: [
{text: 'From:', style: 'subheader'},
{text: invoice.company.name, style: 'boldText'},
{text: invoice.company.address, style: 'normalText'},
{text: invoice.company.city, style: 'normalText'},
{text: 'Phone: ' + invoice.company.phone, style: 'normalText'},
{text: 'Email: ' + invoice.company.email, style: 'normalText'},
{text: 'Web: ' + invoice.company.website, style: 'normalText'}
]
},
{
width: 'auto',
stack: [
{text: 'Bill To:', style: 'subheader'},
{text: invoice.customer.name, style: 'boldText'},
{text: invoice.customer.address, style: 'normalText'},
{text: invoice.customer.city, style: 'normalText'},
{text: 'Contact: ' + invoice.customer.contact_person, style: 'normalText'},
{text: 'Email: ' + invoice.customer.email, style: 'normalText'}
]
}
],
columnGap: 170,
margin: [0, 0, 0, 30]
},
// Item table header
{
canvas: [{type: 'line', x1: 0, y1: 0, x2: 515, y2: 0, lineWidth: 1, lineColor: colors.primary}]
},
{
text: 'Items',
style: 'sectionHeader',
margin: [0, 10, 0, 10]
},
// Line items
{
table: {
headerRows: 1,
widths: ['*', 50, 60, 60, 60],
body: [
// Table header
[
{text: 'Description', style: 'tableHeader', fillColor: colors.background},
{text: 'Quantity', style: 'tableHeader', alignment: 'center', fillColor: colors.background},
{
text: 'Unit Price',
style: 'tableHeader',
alignment: 'right',
fillColor: colors.background
},
{text: 'Tax', style: 'tableHeader', alignment: 'right', fillColor: colors.background},
{text: 'Amount', style: 'tableHeader', alignment: 'right', fillColor: colors.background}
],
// Table body - dynamically add invoice items
...invoice.items.map((item, i) => [
item.description,
{text: item.quantity.toString(), alignment: 'center'},
{text: formatCurrency(item.unit_price), alignment: 'right'},
{text: formatCurrency(item.tax), alignment: 'right'},
{text: formatCurrency(item.amount), alignment: 'right'}
])
]
},
layout: {
hLineWidth: function () {
return 0;
},
vLineWidth: function () {
return 0; // No vertical lines
},
hLineColor: function () {
return colors.border;
},
fillColor: function (rowIndex) {
return (rowIndex % 2 === 0 && rowIndex > 0) ? colors.background : null;
},
paddingLeft: function () {
return 8;
},
paddingRight: function () {
return 8;
},
paddingTop: function () {
return 10;
},
paddingBottom: function () {
return 10;
}
}
},
// Summary section with totals
{
stack: [
{
alignment: 'right',
table: {
widths: ['*', 100],
body: [
[
{text: 'Subtotal:', style: 'tableFooter', alignment: 'right'},
{text: formatCurrency(invoice.subtotal), style: 'tableFooter', alignment: 'right'}
],
[
{text: 'Tax:', style: 'tableFooter', alignment: 'right'},
{text: formatCurrency(invoice.total_tax), style: 'tableFooter', alignment: 'right'}
],
[
{
text: 'Total:',
style: 'tableFooterTotal',
alignment: 'right',
fillColor: colors.primary,
color: 'white'
},
{
text: formatCurrency(invoice.total),
style: 'tableFooterTotal',
alignment: 'right',
fillColor: colors.primary,
color: 'white'
}
]
]
},
layout: {
hLineWidth: function (i) {
return (i === 0 || i === 3) ? 1 : 0;
},
vLineWidth: function () {
return 0;
},
hLineColor: function () {
return colors.border;
},
paddingLeft: function () {
return 10;
},
paddingRight: function () {
return 10;
},
paddingTop: function () {
return 10;
},
paddingBottom: function () {
return 10;
}
},
width: 250,
margin: [0, 20, 0, 0]
}
],
alignment: 'right'
},
// Payment Information section
{
canvas: [{type: 'line', x1: 0, y1: 0, x2: 515, y2: 0, lineWidth: 1, lineColor: colors.primary}],
margin: [0, 30, 0, 10]
},
{text: invoice.notes, color: colors.lightText, alignment: 'center'},
{
columns: [
{
width: '*',
stack: [
{text: 'Payment Information', style: 'subheader'},
{
text: [
'Please make checks payable to: ',
{text: invoice.company.name + '\n', style: 'normalText'},
'Bank Account: ',
{text: '1010 1010 1010 1010', style: 'normalText'}
],
margin: [0, 0, 0, 15]
}
]
}
],
margin: [0, 10, 0, 0]
}
],
styles: {
companyName: {
fontSize: 20,
bold: true
},
companyTagline: {
fontSize: 12,
italics: true
},
invoiceTitle: {
fontSize: 40,
bold: true,
alignment: 'left'
},
sectionHeader: {
fontSize: 14,
bold: true,
color: colors.primary
},
subheader: {
fontSize: 13,
bold: true,
color: colors.primary,
lineHeight: 1.5
},
boldText: {
fontSize: 12,
bold: true,
lineHeight: 1.5
},
normalText: {
fontSize: 10,
color: colors.text,
lineHeight: 1.4
},
tableHeader: {
fontSize: 11,
bold: true,
color: colors.primary
},
tableFooter: {
fontSize: 11,
bold: true
},
tableFooterTotal: {
fontSize: 13,
bold: true
}
},
defaultStyle: {
fontSize: 10,
color: colors.text
},
footer: function (currentPage, pageCount) {
return {
columns: [
{
text: `${invoice.company.name} - ${invoice.company.website}`,
alignment: 'left',
color: colors.lightText,
fontSize: 9,
margin: [40, 0, 0, 0]
},
{
text: `Page ${currentPage} of ${pageCount}`,
alignment: 'right',
color: colors.lightText,
fontSize: 9,
margin: [0, 0, 40, 0]
}
],
margin: [0, 20, 0, 0]
};
}
};
try {
// Generate the PDF
const pdfDoc = printer.createPdfKitDocument(docDefinition);
// Pipe to file
pdfDoc.pipe(fs.createWriteStream('invoice.pdf'));
pdfDoc.end();
console.log('Invoice PDF generated successfully!');
} catch (error) {
console.error('Error generating PDF:', error);
}
}
// Execute the function
generateInvoicePDF();
This code generates a professional invoice with the following features:
- Company and customer information blocks.
- Detailed item listing with quantities, prices, and taxes.
- Automatic calculations for subtotals and totals.
- Payment information section.
- Styled footer.
Generated invoice preview:
Best Practices for PDFMake in Node.js
To ensure your PDF generation is reliable, maintainable, and efficient, follow these best practices:
-
Error handling: Always implement proper error handling to catch and manage exceptions during PDF generation:
try {
const pdfDoc = printer.createPdfKitDocument(docDefinition);
pdfDoc.pipe(fs.createWriteStream('output.pdf'));
pdfDoc.end();
} catch (error) {
console.error('PDF generation failed:', error);
// Handle error appropriately
} -
Template separation: Keep your document templates separate from your business logic:
// templates/invoice-template.js
module.exports = function createInvoiceTemplate(data) {
return {
content: [
{ text: 'INVOICE', style: 'header' },
// Template structure using data
],
styles: {
// Style definitions
}
};
};
// pdf-generator.js
const createInvoiceTemplate = require('./templates/invoice-template');
function generateInvoice(invoiceData) {
const docDefinition = createInvoiceTemplate(invoiceData);
const pdfDoc = printer.createPdfKitDocument(docDefinition);
// Generate PDF
} -
Font management: Bundle fonts with your application and use absolute paths for reliability:
const fonts = {
Roboto: {
normal: path.resolve(__dirname, '../assets/fonts/Roboto-Regular.ttf'),
bold: path.resolve(__dirname, '../assets/fonts/Roboto-Medium.ttf'),
italics: path.resolve(__dirname, '../assets/fonts/Roboto-Italic.ttf'),
bolditalics: path.resolve(__dirname, '../assets/fonts/Roboto-MediumItalic.ttf')
}
};
Conclusion
PDFMake is a powerful and flexible library for generating PDF documents in Node.js applications. Its declarative approach to document definition makes it intuitive to use, while its extensive feature set allows for creating complex and professional-looking documents.
By leveraging PDFMake in your Node.js applications, you can generate a wide range of PDF documents, from simple reports to complex invoices with custom layouts. The examples and best practices covered in this guide provide a solid foundation for implementing PDFMake in your projects.
Keep your margins well-defined and your fonts properly loaded! 🚀