htmlnode / server.js
srivatsavdamaraju's picture
Update server.js
953aa50 verified
const express = require("express");
const cors = require("cors");
const puppeteer = require("puppeteer");
const app = express();
const PORT = process.env.PORT || 7860; // Hugging Face uses port 7860
// Middleware
app.use(cors());
app.use(express.json({ limit: "5mb" }));
// Root endpoint - Health check
app.get("/", (_req, res) => {
res.json({
status: "ok",
message: "HTML to PDF API is running",
version: "1.0.0",
endpoints: {
health: "GET / or GET /health",
convert: "POST /api/html-to-pdf"
},
usage: {
method: "POST",
url: "/api/html-to-pdf",
body: {
html_code: "Your HTML content here (with inline styles and complete structure)",
pdf_options: {
format: "A4",
printBackground: true,
margin: {
top: "0mm",
right: "0mm",
bottom: "0mm",
left: "0mm"
}
}
},
notes: [
"HTML should include all styles inline or in <style> tags",
"Use @page CSS rules for page-specific styling",
"Margins default to 0mm - add padding in your HTML instead",
"Charts/images will be rendered - allow 1.5s load time"
]
}
});
});
// Health check endpoint
app.get("/health", (_req, res) => {
res.json({
status: "healthy",
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// Main PDF conversion endpoint
app.post("/api/html-to-pdf", async (req, res) => {
let browser;
const startTime = Date.now();
try {
const { html_code: htmlContent, pdf_options: pdfOptions } = req.body || {};
// Validate input
if (!htmlContent || !htmlContent.trim()) {
return res.status(400).json({
error: "html_code is required",
success: false
});
}
console.log(`[${new Date().toISOString()}] Starting PDF generation...`);
// Launch browser - using Puppeteer's bundled Chromium
browser = await puppeteer.launch({
args: [
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-dev-shm-usage",
"--disable-gpu",
"--disable-software-rasterizer",
"--disable-extensions",
"--disable-background-networking",
"--disable-default-apps",
"--disable-sync",
"--metrics-recording-only",
"--mute-audio",
"--no-first-run"
],
headless: true
// Remove executablePath - let Puppeteer use its bundled Chromium
});
const page = await browser.newPage();
// Set content with PDF-optimized styling
await page.setContent(
`<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<style>
@page {
margin: 0;
size: A4;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
color-adjust: exact;
}
img, svg, canvas {
max-width: 100%;
height: auto;
display: block;
}
table {
border-collapse: collapse;
width: 100%;
}
</style>
</head>
<body>
${htmlContent}
</body>
</html>`,
{
waitUntil: ["load", "domcontentloaded", "networkidle0"],
timeout: 30000
}
);
// Wait for fonts and images to load
await page.evaluateHandle('document.fonts.ready');
// Wait for any dynamic content (charts, images, etc.)
await new Promise(resolve => setTimeout(resolve, 1500));
// Generate PDF with optimized options
const defaultPdfOptions = {
format: "A4",
printBackground: true,
margin: {
top: "0mm",
right: "0mm",
bottom: "0mm",
left: "0mm"
},
preferCSSPageSize: true
};
const pdfBuffer = await page.pdf({
...defaultPdfOptions,
...(pdfOptions || {}),
encoding: 'binary'
});
await browser.close();
browser = null;
const processingTime = Date.now() - startTime;
console.log(`[${new Date().toISOString()}] PDF generated successfully in ${processingTime}ms`);
// Convert Buffer to base64 properly
const pdfBase64 = Buffer.from(pdfBuffer).toString('base64');
res.json({
pdf_base64: pdfBase64,
success: true,
processing_time_ms: processingTime,
pdf_size_bytes: pdfBuffer.length
});
} catch (err) {
console.error(`[${new Date().toISOString()}] PDF Generation Error:`, err);
if (browser) {
try {
await browser.close();
} catch (closeErr) {
console.error("Browser close error:", closeErr);
}
}
res.status(500).json({
error: "Failed to generate PDF",
details: err.message,
success: false
});
}
});
// 404 handler
app.use((_req, res) => {
res.status(404).json({
error: "Endpoint not found",
available_endpoints: [
"GET /",
"GET /health",
"POST /api/html-to-pdf"
]
});
});
// Start server
app.listen(PORT, "0.0.0.0", () => {
console.log(`
╔════════════════════════════════════════╗
β•‘ HTML to PDF API Server β•‘
β•‘ Status: Running βœ… β•‘
β•‘ Port: ${PORT} β•‘
β•‘ URL: http://0.0.0.0:${PORT} β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
`);
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM signal received: closing HTTP server');
process.exit(0);
});
process.on('SIGINT', () => {
console.log('SIGINT signal received: closing HTTP server');
process.exit(0);
});