· 8 Min. Lesezeit
PDF-Reports in Node.js ohne Puppeteer erzeugen
Ohne Puppeteer und Chromium: PDF-Reports in Node.js per REST an QuartzAPI und Buffer über Express ausliefern.
Puppeteer and headless Chrome instances are notorious resource hogs in Node.js environments. Running Chromium inside Docker containers inflates image sizes, consumes high RAM (often causing out-of-memory crashes on low-tier cloud instances), and adds severe cold-start latency.
By switching to QuartzAPI, you eliminate headless browser dependencies entirely. Your Node.js service sends report JSON over REST; the cloud engine renders the PDF from a Visual Builder template.
Prerequisites
- Node.js v16+ (CommonJS or ES Modules)
node-fetchor nativefetch(built-in from Node v18+)- A QuartzAPI API key and an active template code (e.g.
SALES_REPORT_Q3)
Step 1: install dependencies (optional)
If you use native fetch (Node 18+), no extra packages are required for the HTTP call.
For older Node versions, install node-fetch:
npm install node-fetch
Express is only needed if you expose an HTTP route that returns the PDF to the browser
(npm install express).
Step 2: build the API integration service
Create a dedicated module (pdfService.js) that calls
generate-document, then downloads the PDF bytes.
QuartzAPI returns JSON with documentId / downloadUrl — not a raw PDF body
on the first request.
// pdfService.js
const QUARTZ_GENERATE_URL =
'https://backend.quartzapi.com/index.php?r=api/v1-jobs/generate-document';
const QUARTZ_DOWNLOAD_URL =
'https://backend.quartzapi.com/index.php?r=api/v1-documents/download';
const API_KEY = process.env.QUARTZ_API_KEY;
/**
* Generates a PDF buffer from dynamic report data.
* @param {string} templateCode - Visual Builder template code.
* @param {object} reportData - Report parameters (typically under master / items).
* @returns {Promise<Buffer>}
*/
async function generateReportPdf(templateCode, reportData) {
const generateRes = await fetch(QUARTZ_GENERATE_URL, {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
templateCode,
outputFormat: 'pdf',
externalId: reportData.externalId || undefined,
data: reportData,
}),
});
if (!generateRes.ok) {
const errorText = await generateRes.text();
throw new Error(`QuartzAPI Error [${generateRes.status}]: ${errorText}`);
}
const payload = await generateRes.json();
const documentId = payload?.result?.documentId;
const downloadUrl =
payload?.result?.downloadUrl ||
`${QUARTZ_DOWNLOAD_URL}&uid=${encodeURIComponent(documentId || '')}`;
if (!documentId && !payload?.result?.downloadUrl) {
throw new Error('QuartzAPI response missing documentId / downloadUrl');
}
const pdfRes = await fetch(downloadUrl, {
method: 'GET',
headers: {
Authorization: `Bearer ${API_KEY}`,
},
});
if (!pdfRes.ok) {
const errorText = await pdfRes.text();
throw new Error(`QuartzAPI download Error [${pdfRes.status}]: ${errorText}`);
}
const arrayBuffer = await pdfRes.arrayBuffer();
return Buffer.from(arrayBuffer);
}
module.exports = { generateReportPdf };
Bind placeholders in the Visual Builder to keys such as master.report_title and
iterate Details from master.items (or your mapped array).
More on the JSON shape:
generate PDF invoices from JSON.
Step 3: Express.js controller example
Serve the generated report dynamically from an Express HTTP route:
// server.js
const express = require('express');
const { generateReportPdf } = require('./pdfService');
const app = express();
app.get('/reports/monthly-sales', async (req, res) => {
try {
const salesData = {
master: {
report_title: 'Monthly Sales Analytics - Q3 2026',
generated_at: new Date().toISOString(),
total_revenue: '$45,210.00',
active_subscriptions: 312,
churn_rate: '1.2%',
items: [
{ name: 'Enterprise Plan', sales: 120, revenue: '$24,000' },
{ name: 'Pro Plan', sales: 192, revenue: '$21,210' },
],
},
};
const pdfBuffer = await generateReportPdf('SALES_REPORT_Q3', salesData);
res.setHeader('Content-Type', 'application/pdf');
res.setHeader(
'Content-Disposition',
'inline; filename=sales-report.pdf'
);
res.send(pdfBuffer);
} catch (error) {
console.error('PDF Generation Failed:', error);
res.status(500).json({ error: 'Failed to generate report' });
}
});
app.listen(3000, () => console.log('Server running on port 3000'));
Set QUARTZ_API_KEY in the environment (never hard-code secrets in source).
Use Content-Disposition: attachment if you prefer a forced download instead of inline preview.
Why ditch Puppeteer for an API?
| Metric | Puppeteer / Headless Chrome | QuartzAPI |
|---|---|---|
| Docker image size | ~1 GB+ (includes Chromium) | ~50 MB (standard Node) |
| RAM usage | ~100–500 MB per render | ~2 MB (HTTP request payload) |
| Concurrency | Limited by CPU/RAM on your box | Handled by the API fleet |
| Cold start | Chromium boot latency | One HTTP round-trip |
Conclusions
If your Node.js service only needs “JSON in, PDF out”, Puppeteer is usually the wrong tool. Keep the container slim, leave Chromium out of Docker, and let QuartzAPI own layout and pagination.
Ready to remove Puppeteer from production?
Create a free QuartzAPI Beta account, design the report template once, and wire
pdfService.js into your Express app.
Ready-to-use snippets
- Generate:
POST https://backend.quartzapi.com/index.php?r=api/v1-jobs/generate-documentwithtemplateCode+data. - Auth:
Authorization: Bearer ${process.env.QUARTZ_API_KEY}. - Download:
result.downloadUrlorGET …/v1-documents/download&uid=…. - Docs: Web API documentation.