Tuesday 20 February 2024

Converting Dynamic HTML to PDF in Node.js with Puppeteer


In this tutorial, we'll learn how to convert HTML content to a PDF file using Puppeteer in a Node.js environment. Puppeteer is a Node library that provides a high-level API to control headless Chrome or Chromium over the DevTools Protocol, making it a powerful tool for automating web tasks, including generating PDF files from HTML content.


Converting Dynamic HTML to PDF in Node.js with Puppeteer


Prerequisites


Before we begin, ensure you have the following installed:

  • Node.js (with npm)
  • MySQL (for the database setup, optional for this tutorial)
  • Basic understanding of Node.js and JavaScript

Setting up the Project


First, let's set up a Node.js project and install the necessary dependencies.

Create a new directory for your project and navigate into it:

	
		mkdir html-to-pdf-nodejs
		cd html-to-pdf-nodejs
	

Initialize a new Node.js project:

	
		npm init -y
	

Install the required dependencies (Express, MySQL, Puppeteer, Open):

	
		npm install express mysql2 puppeteer open
	

Database Structure


For convert dynamic HTML to PDF we need to fetch data from MySQL table. So run below sql script which will create sampledata table in your database.

	
		CREATE TABLE `sampledata` (
		  `id` mediumint unsigned NOT NULL AUTO_INCREMENT,
		  `name` varchar(255) DEFAULT NULL,
		  `phone` varchar(100) DEFAULT NULL,
		  `email` varchar(255) DEFAULT NULL,
		  `address` varchar(255) DEFAULT NULL,
		  `postalZip` varchar(10) DEFAULT NULL,
		  `region` varchar(50) DEFAULT NULL,
		  `country` varchar(100) DEFAULT NULL,
		  PRIMARY KEY (`id`)
		) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
		
		INSERT INTO `sampledata` VALUES (1,'Stephen Taylor','1-802-830-1866','auctor.nunc@hotmail.com','5659 Luctus Rd.','21408','Dadra and Nagar Haveli','Poland'),(2,'Mason George','1-343-545-7768','mauris@aol.edu','Ap #221-6699 Lacus. St.','921307','FATA','Costa Rica'),(3,'Hasad Donaldson','1-666-557-3187','et.euismod@google.edu','Ap #383-4682 Ornare Av.','818143','Kinross-shire','United Kingdom'),(4,'Hall Holland','(805) 418-1538','tempor.est@icloud.com','P.O. Box 358, 7624 Tincidunt Avenue','486262','Madrid','Indonesia'),(5,'Adam Hampton','1-421-241-5178','cubilia.curae@google.ca','P.O. Box 161, 1859 Iaculis Av.','44837','Brussels Hoofdstedelijk Gewest','South Korea'),(6,'Elizabeth Allison','(538) 834-6212','elit.dictum@hotmail.edu','867-6580 At St.','33668','Jeju','Pakistan'),(7,'Neve Barber','1-552-311-0208','vestibulum.nec.euismod@google.com','224-8122 Donec Road','51174','Santa Catarina','Netherlands'),(8,'Fatima Pope','(619) 278-1176','nulla.integer@hotmail.couk','649-1054 Ipsum Rd.','748321','Bahia','Austria'),(9,'Shad Cobb','1-778-327-3349','dignissim.maecenas.ornare@outlook.edu','6983 Magna. Rd.','2163','Xīběi','Philippines'),(10,'Zephr Ruiz','1-544-316-1144','luctus.ipsum@google.ca','634-2043 Cras Avenue','3845','Mecklenburg-Vorpommern','Sweden'),(11,'Juliet Lynn','(375) 573-6793','proin.dolor.nulla@yahoo.net','Ap #179-6441 Cum Rd.','71829','Calabria','Turkey'),(12,'Candice Medina','1-441-292-1278','integer.vulputate@icloud.org','953-188 Et Rd.','440326','Vorarlberg','Australia'),(13,'Rhea Roach','(635) 768-6867','vitae.velit@icloud.edu','543-5616 Sem Av.','869566','Burgenland','Canada'),(14,'Dean Hendrix','(362) 760-8321','nunc.pulvinar@protonmail.ca','Ap #905-5267 Arcu Street','6755-4242','National Capital Region','Indonesia'),(15,'Malachi Mitchell','1-586-881-4174','eget.lacus.mauris@icloud.ca','P.O. Box 183, 1479 Massa. St.','367458','Västra Götalands län','Indonesia'),(21,'Gabriel Acevedo','1-215-463-4511','dictum.cursus@outlook.com','5681 Sit Rd.','28846','Lombardia','Italy'),(22,'Beau Norris','1-563-287-4004','eros@google.edu','Ap #533-2583 Duis Rd.','R8T 1S4','Eastern Visayas','United States'),(23,'Kylan Mckinney','1-533-833-5567','eleifend.vitae@hotmail.net','333-2972 Nec, Road','72866','Leinster','Australia'),(24,'Stone Parsons','1-584-246-2228','aenean@outlook.com','923-5509 Etiam Street','Y1R 5X7','Zamboanga Peninsula','Nigeria'),(25,'Neve Sweet','(764) 167-2572','tellus.aenean@aol.net','558-1070 Sed, St.','1023 PS','Vienna','United Kingdom');
	

Creating the Server and HTML Template


Now, let's create a basic Express server that serves an HTML page with sample data fetched from a MySQL database. We'll also include a button to trigger the conversion of the HTML to a PDF file.

Create a file named app.js with the following content:

app.js
	
		const express = require('express');
		const mysql = require('mysql2');
		//Add puppeteer library
		const puppeteer = require('puppeteer');
		const app = express();
		const port = 3000;
		app.use(express.json());
		//Serve static files (including index.html) from the root directory
		app.use(express.static(__dirname));
		const database = mysql.createConnection({
			host : 'localhost',
			user : 'root',
			password : '123456789',
			database : 'testing'
		});

		database.connect((error)=> {
			if(error){
				console.error('Error connecting to MySQL : ', error);
			} else{
				console.log('Connected to MySQL database');
			}
		});

		app.get('/data', (request, response) => {
			response.sendFile(__dirname + '/data.html');
		});

		app.post('/getData', (request, response) => {
			database.query('SELECT * FROM sampledata', async (error, results) => {
				response.status(200).json(results);
			});
		});

		app.listen(port, () => {
			console.log(`Server is running on http://localhost:${port}`);   
		});
	

Create a file named data.html with the following content:

	
		<!doctype html>
		<html lang="en">
			<head>
				<!-- Required meta tags -->
				<meta charset="utf-8">
				<meta name="viewport" content="width=device-width, initial-scale=1">

				<!-- Bootstrap CSS -->
				<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

				<title>How to Convert HTML to PDF in Node.js using Puppeteer</title>
			</head>
			<body>
				
				<div class="container">
					<h1 class="text-center mb-5 mt-5">How to Convert HTML to PDF in Node.js using Puppeteer</h1>
					<div class="card">
						<div class="card-header">
							<div class="row">
								<div class="col col-6">Sample Data</div>
								<div class="col col-6">
									<a href="/convertPDF" class="btn btn-primary btn-sm float-end">Download in PDF</a>
								</div>
							</div>
						</div>
						<div class="card-body">
							<div class="table-responsive">
								<table class="table table-bordered table-striped">
									<thead>
										<tr>
											<th>Name</th>
											<th>Phone</th>
											<th>Email</th>
											<th>Address</th>
											<th>Zip</th>
											<th>Region</th>
											<th>Country</th>
										</tr>
									</thead>
									<tbody id="dataArea"></tbody>
								</table>
							</div>
						</div>
					</div>
				</div>

				<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
			</body>
		</html>

		<script>
			getData();
			function getData(){
				fetch('/getData', {
					method: 'POST'
				})
				.then(response => {
					return response.json();
				})
				.then(data => {
					if(data.length > 0){
						let html = '';
						for(let i = 0; i < data.length; i++){
							html += `
							<tr>
								<td>${data[i].name}</td>
								<td>${data[i].phone}</td>
								<td>${data[i].email}</td>
								<td>${data[i].address}</td>
								<td>${data[i].postalZip}</td>
								<td>${data[i].region}</td>
								<td>${data[i].country}</td>
							</tr>
							`;
						}
						document.getElementById('dataArea').innerHTML = html;
					}
				})
				.catch(error => {
					// Handle errors
					//console.error(error);
					alert(error);
				});
			}
		</script>
	

Save this code in the root directory of your project.

HTML to PDF Conversion


We'll define a function convertHTMLToPDF in app.js file that takes HTML content as input, converts it to a PDF file using Puppeteer, and opens the generated PDF file.

app.js
	
		async function convertHTMLToPDF(htmlContent, pdfFilePath, margins = {top: '10mm', right: '10mm', bottom: '10mm', left: '10mm'}){
			const browser = await puppeteer.launch();
			const page = await browser.newPage();
			// Set the page content
			await page.setContent(htmlContent);
			// Generate PDF
			await page.pdf({ path : pdfFilePath, format : 'A4', margin : margins });
			// Open the generated PDF file in the default PDF viewer
			const open = await import('open');
			await open.default(pdfFilePath);
			//close the browser
			await browser.close();
		}
	

Serving HTML and Triggering Conversion


Finally, we'll set up routes to fetch data from MySQL table and convert into HTML and serve the HTML data and trigger the conversion to PDF when the user clicks on Download in PDF button.

app.js
	
		app.get('/convertPDF', async (request, response)=>{
			database.query('SELECT * FROM sampledata', async (error, results) => {
				let html = '';
				if(results.length > 0){
					html += `
					<table width="100%" border="1" cellpadding="5" cellspacing="0">
						<tr>
							<th width="20%">Name</th>
							<th width="10%">Phone</th>
							<th width="20%">Email</th>
							<th width="20%">Address</th>
							<th width="10%">Zip</th>
							<th width="10%">Region</th>
							<th width="10%">Country</th>
						</tr>
					`;
					results.map((row)=>{
						html += `
						<tr>
							<th>${row.name}</th>
							<th>${row.phone}</th>
							<th>${row.email}</th>
							<th>${row.address}</th>
							<th>${row.postalZip}</th>
							<th>${row.region}</th>
							<th>${row.country}</th>
						</tr>
						`;
					});
					html += `
					</table>
					`;
				}
				await convertHTMLToPDF(html, 'data.pdf');
			});
		});
	

Running the Server


To run the server, execute the following command:

	
		node app.js
	

Visit http://localhost:3000/data in your web browser to see the sample data table. Click the "Download in PDF" button to convert the HTML table to a PDF file.

Conclusion


Congratulations! You've learned how to convert HTML content to a PDF file in Node.js using Puppeteer. This technique can be useful for generating PDF reports, invoices, or any other printable documents from dynamic HTML content.

Feel free to customize the code to suit your specific requirements and explore additional features offered by Puppeteer for web automation and PDF generation.

Complete Source Code


app.js
	
		const express = require('express');
		const mysql = require('mysql2');
		//Add puppeteer library
		const puppeteer = require('puppeteer');
		const app = express();
		const port = 3000;
		app.use(express.json());
		//Serve static files (including index.html) from the root directory
		app.use(express.static(__dirname));
		const database = mysql.createConnection({
			host : 'localhost',
			user : 'root',
			password : '123456789',
			database : 'testing'
		});

		database.connect((error)=> {
			if(error){
				console.error('Error connecting to MySQL : ', error);
			} else{
				console.log('Connected to MySQL database');
			}
		});

		app.get('/data', (request, response) => {
			response.sendFile(__dirname + '/data.html');
		});

		app.post('/getData', (request, response) => {
			database.query('SELECT * FROM sampledata', async (error, results) => {
				response.status(200).json(results);
			});
		});

		async function convertHTMLToPDF(htmlContent, pdfFilePath, margins = {top: '10mm', right: '10mm', bottom: '10mm', left: '10mm'}){
			const browser = await puppeteer.launch();
			const page = await browser.newPage();
			// Set the page content
			await page.setContent(htmlContent);
			// Generate PDF
			await page.pdf({ path : pdfFilePath, format : 'A4', margin : margins });
			// Open the generated PDF file in the default PDF viewer
			const open = await import('open');
			await open.default(pdfFilePath);
			//close the browser
			await browser.close();
		}

		app.get('/convertPDF', async (request, response)=>{
			database.query('SELECT * FROM sampledata', async (error, results) => {
				let html = '';
				if(results.length > 0){
					html += `
					<table width="100%" border="1" cellpadding="5" cellspacing="0">
						<tr>
							<th width="20%">Name</th>
							<th width="10%">Phone</th>
							<th width="20%">Email</th>
							<th width="20%">Address</th>
							<th width="10%">Zip</th>
							<th width="10%">Region</th>
							<th width="10%">Country</th>
						</tr>
					`;
					results.map((row)=>{
						html += `
						<tr>
							<th>${row.name}</th>
							<th>${row.phone}</th>
							<th>${row.email}</th>
							<th>${row.address}</th>
							<th>${row.postalZip}</th>
							<th>${row.region}</th>
							<th>${row.country}</th>
						</tr>
						`;
					});
					html += `
					</table>
					`;
				}
				await convertHTMLToPDF(html, 'data.pdf');
			});
		});


		app.listen(port, () => {
			console.log(`Server is running on http://localhost:${port}`);   
		});
	

data.html
	
		<!doctype html>
		<html lang="en">
			<head>
				<!-- Required meta tags -->
				<meta charset="utf-8">
				<meta name="viewport" content="width=device-width, initial-scale=1">

				<!-- Bootstrap CSS -->
				<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

				<title>How to Convert HTML to PDF in Node.js using Puppeteer</title>
			</head>
			<body>
				
				<div class="container">
					<h1 class="text-center mb-5 mt-5">How to Convert HTML to PDF in Node.js using Puppeteer</h1>
					<div class="card">
						<div class="card-header">
							<div class="row">
								<div class="col col-6">Sample Data</div>
								<div class="col col-6">
									<a href="/convertPDF" class="btn btn-primary btn-sm float-end">Download in PDF</a>
								</div>
							</div>
						</div>
						<div class="card-body">
							<div class="table-responsive">
								<table class="table table-bordered table-striped">
									<thead>
										<tr>
											<th>Name</th>
											<th>Phone</th>
											<th>Email</th>
											<th>Address</th>
											<th>Zip</th>
											<th>Region</th>
											<th>Country</th>
										</tr>
									</thead>
									<tbody id="dataArea"></tbody>
								</table>
							</div>
						</div>
					</div>
				</div>

				<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
			</body>
		</html>

		<script>
			getData();
			function getData(){
				fetch('/getData', {
					method: 'POST'
				})
				.then(response => {
					return response.json();
				})
				.then(data => {
					if(data.length > 0){
						let html = '';
						for(let i = 0; i < data.length; i++){
							html += `
							<tr>
								<td>${data[i].name}</td>
								<td>${data[i].phone}</td>
								<td>${data[i].email}</td>
								<td>${data[i].address}</td>
								<td>${data[i].postalZip}</td>
								<td>${data[i].region}</td>
								<td>${data[i].country}</td>
							</tr>
							`;
						}
						document.getElementById('dataArea').innerHTML = html;
					}
				})
				.catch(error => {
					// Handle errors
					//console.error(error);
					alert(error);
				});
			}
		</script>
	


0 comments:

Post a Comment