Skip to content

Instantly share code, notes, and snippets.

@digitaldrreamer
Created September 15, 2025 10:55
Show Gist options
  • Select an option

  • Save digitaldrreamer/174b484964f600e358013f34a5cb5712 to your computer and use it in GitHub Desktop.

Select an option

Save digitaldrreamer/174b484964f600e358013f34a5cb5712 to your computer and use it in GitHub Desktop.
import express from "express"
import puppeteer from "puppeteer"
const router = express.Router()
/**
* Test endpoint that generates a Puppeteer PDF of the transaction document demo page
* Uses page.print() with PDF settings for high-quality output
*
* Query Parameters:
* - type: 'completed' | 'pending' | 'failed' | 'refunded' (default: 'completed')
* - format: 'A4' | 'Letter' (default: 'Letter')
* - orientation: 'portrait' | 'landscape' (default: 'portrait')
* - margin: 'none' | 'minimum' | 'default' (default: 'default')
* - scale: number between 0.1 and 2 (default: 1)
* - displayHeaderFooter: boolean (default: false)
* - printBackground: boolean (default: true)
*/
router.get("/generate-pdf", async (req, res) => {
const timer = createTimer("pdf-generation")
try {
// * Parse query parameters
const type = req.query.type || "completed"
const format = req.query.format || "Letter"
const orientation = req.query.orientation || "portrait"
const margin = req.query.margin || "default"
const scale = parseFloat(req.query.scale || "1")
const displayHeaderFooter = req.query.displayHeaderFooter === "true"
const printBackground = req.query.printBackground !== "false"
// * Validate parameters
const validTypes = ["completed", "pending", "failed", "refunded"]
const validFormats = ["A4", "Letter"]
const validOrientations = ["portrait", "landscape"]
const validMargins = ["none", "minimum", "default"]
if (!validTypes.includes(type)) {
return res.status(400).json({
success: false,
error: `Invalid type. Must be one of: ${validTypes.join(", ")}`,
})
}
if (!validFormats.includes(format)) {
return res.status(400).json({
success: false,
error: `Invalid format. Must be one of: ${validFormats.join(", ")}`,
})
}
if (!validOrientations.includes(orientation)) {
return res.status(400).json({
success: false,
error: `Invalid orientation. Must be one of: ${validOrientations.join(", ")}`,
})
}
if (!validMargins.includes(margin)) {
return res.status(400).json({
success: false,
error: `Invalid margin. Must be one of: ${validMargins.join(", ")}`,
})
}
if (scale < 0.1 || scale > 2) {
return res.status(400).json({
success: false,
error: "Scale must be between 0.1 and 2",
})
}
// * Get the base URL for the demo page
const baseUrl = `${req.protocol}://${req.get("host")}`
const demoUrl = `${baseUrl}/demo/transaction-document?type=${type}`
console.log(`Generating PDF for transaction type: ${type}`)
console.log(`Demo URL: ${demoUrl}`)
// * Launch Puppeteer
const browser = await puppeteer.launch({
headless: "new",
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || undefined,
args: [
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-dev-shm-usage",
"--disable-accelerated-2d-canvas",
"--no-first-run",
"--no-zygote",
"--disable-gpu",
"--disable-web-security",
"--disable-features=VizDisplayCompositor",
"--single-process",
"--disable-background-timer-throttling",
"--disable-backgrounding-occluded-windows",
"--disable-renderer-backgrounding",
"--disable-extensions",
"--disable-plugins",
"--disable-images",
"--disable-default-apps",
"--disable-sync",
"--disable-translate",
"--hide-scrollbars",
"--mute-audio",
"--no-default-browser-check",
"--no-pings",
"--disable-logging",
"--disable-background-networking",
"--disable-client-side-phishing-detection",
"--disable-component-extensions-with-background-pages",
"--disable-domain-reliability",
"--disable-features=TranslateUI",
"--disable-hang-monitor",
"--disable-ipc-flooding-protection",
"--disable-popup-blocking",
"--disable-prompt-on-repost",
"--disable-web-resources",
"--metrics-recording-only",
"--safebrowsing-disable-auto-update",
"--enable-automation",
"--password-store=basic",
"--use-mock-keychain",
],
timeout: 30000,
protocolTimeout: 30000,
})
try {
const page = await browser.newPage()
await page.setViewport({
width: 1200,
height: 800,
deviceScaleFactor: 2,
})
await page.evaluateOnNewDocument(() => {
const style = document.createElement("style")
style.textContent = `
body { background: white !important; }
main { background: white !important; }
.document-container { background: white !important; }
`
document.head.appendChild(style)
})
await page.goto(demoUrl, {
waitUntil: "networkidle0",
timeout: 30000,
})
await page.waitForSelector(".document-container", { timeout: 10000 })
await new Promise((resolve) => setTimeout(resolve, 1000))
const pdfBuffer = await page.pdf({
format,
landscape: orientation === "landscape",
margin: {
top: margin === "none" ? "0" : margin === "minimum" ? "0.2in" : "0.5in",
right: margin === "none" ? "0" : margin === "minimum" ? "0.2in" : "0.5in",
bottom: margin === "none" ? "0" : margin === "minimum" ? "0.2in" : "0.5in",
left: margin === "none" ? "0" : margin === "minimum" ? "0.2in" : "0.5in",
},
scale,
displayHeaderFooter,
printBackground,
preferCSSPageSize: false,
tagged: true,
outline: false,
omitBackground: !printBackground,
timeout: 30000,
})
const duration = timer.end()
console.log(`PDF generated successfully in ${duration.toFixed(2)}ms`)
res.setHeader("Content-Type", "application/pdf")
res.setHeader(
"Content-Disposition",
`attachment; filename="transaction-${type}-${Date.now()}.pdf"`
)
res.setHeader("Content-Length", pdfBuffer.length.toString())
res.setHeader("Cache-Control", "no-cache")
return res.send(pdfBuffer)
} finally {
await browser.close()
}
} catch (error) {
const duration = timer.end()
console.error("PDF generation failed:", error)
return res.status(500).json({
success: false,
error: error.message,
duration: `${duration.toFixed(2)}ms`,
})
}
})
/**
* Performance timer utility
*/
function createTimer(label) {
const startTime = performance.now()
return {
end: () => {
const duration = performance.now() - startTime
console.log(`Performance: ${label} - ${duration.toFixed(2)}ms`)
return duration
},
}
}
export default router
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment