I’m currently working on implementing a PDF generation feature where the frontend sends a user ID to the backend, which then uses Puppeteer to create the PDF. As a junior frontend developer, this is a challenging task for me. My supervisor has set up Entities and Serializers system to handle all data going to and from the backend, which helps streamline our development process. I want to send the userId to the backend when the user press the button to download the PDF, the backend then creates a PDF using handlebars or html and puppeeter, then return this PDF to the frontend allowing the user to download the PDF.
This is the formula in the front end
interface PropsForFxclusion {
id: string;
nome?: string;
finalized?: boolean;
pdf: Buffer;
}
async function handleFindJsonToPDF(id: string) {
const token = Cookies.get('auth_token');
const backendApi = new BackendApiGet(token);
setIsLoadingPdf(true);
try {
const response = await backendApi.generatePDF(id);
if (!Array.isArray(response) || response.length === 0) {
throw new Error('Invalid response format or empty data');
}
const pdfBuffer = response[0].pdf;
const blob = new Blob([pdfBuffer], { type: 'application/pdf' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = `${id}.pdf`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
} catch (error) {
handleApiErrors(error, setError, setMsgError);
} finally {
setIsLoadingPdf(false);
}
}
<div className={styles.container}>
{isLoadingPdf && <ComponentInfos message={'Your pdf is being generated'} />}
{props.finalized ? (
<Tooltip text="Download PDF">
<Action
icon={renderIcon(MdOutlineFileDownload)}
onClick={() => handleFindJsonToPDF(props.id)}
/>
</Tooltip>
Backend code
const fs = require('fs-extra');
const hbs = require('handlebars');
const puppeteer = require('puppeteer');
const path = require('path');
const connection = require("../../connection");
async function fetchPDFDataById(id) {
const query = `
SELECT a.*, c.*
FROM files f
JOIN files_criteria c ON f.id = c.id_files
JOIN users u ON a.id_ee = u.id_ee
WHERE a.id_user = $1
`;
const { rows } = await connection.query(query, [id]);
return rows;
}
async function compileHTML(templatePath, data) {
const template = await fs.readFile(templatePath, 'utf8');
const compiledTemplate = hbs.compile(template);
return compiledTemplate(data);
}
async function generatePDF(htmlContent) {
try {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
const page = await browser.newPage();
await page.goto('about:blank', { timeout: 60000 });
await page.setContent(htmlContent);
const pdfBuffer = await page.pdf({
format: 'A4',
printBackground: true,
timeout: 60000
});
await browser.close();
return pdfBuffer;
} catch (error) {
console.error('Error generating Puppeteer:', error);
throw error;
}
}
async function GeneratePDF(req, res) {
const { id } = req.user;
console.log("arrived here")
console.log(id)
if (!id) {
return res.status(400).json({ menssage: "Missing user ID" });
}
try {
const dataRows = await fetchPDFDataById(id);
if (!dataRows || dataRows.length === 0) {
return res.status(404).json({ ,message: "No file for this user" });
}
const data = dataRows.map(row => ({
dateofobservation: row.dateofobservation,
}));
const templatePath = path.join(__dirname, 'pdf.hbs');
const htmlContent = await compileHTML(templatePath, { data });
const pdfBuffer = await generatePDF(htmlContent);
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', 'attachment; filename=pdfgenerate.pdf');
console.log(typeof(pdfBuffer))
return res.send(pdfBuffer);
} catch (error) {
console.error('Error generating PDF:', error);
return res.status(500).json({ message: "Failing generating PDF" });
}
}
module.exports = {
GeneratePDF,
};
HBS file
<html>
<head>
<meta charset="utf-8">
<title>PDF Template</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 20px;
}
.header {
text-align: center;
margin-bottom: 20px;
}
.data-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.data-table th, .data-table td {
border: 1px solid #ddd;
padding: 8px;
}
.data-table th {
background-color: #f2f2f2;
}
</style>
</head>
<body>
<div class="header">
<h1>PDF Report</h1>
</div>
<table class="data-table">
<thead>
<tr>
<th>Date of Observation</th>
</tr>
</thead>
<tbody>
{{#each data}}
<tr>
<td>{{dateofobservation}}</td>
</tr>
{{/each}}
</tbody>
</table>
</body>
</html>
- I am testing sending only one parameter
Is generating a PDF that I cannot even open
I don’t even know what can I try anymore, I am really stuck.
The error I was getting before was
Type 'EntitiesGeneratePDF[]' is not assignable to type 'BlobPart'.
Type 'EntitiesGeneratePDF[]' is missing the following properties from type 'Blob': size, type, arrayBuffer, stream, textts(2322)
Gustavo Cortez is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
2