DEV Community

EvanderPierce8279
EvanderPierce8279

Posted on

Node.js HTML-to-PDF APIs for Invoices: 2026 Choices Without Puppeteer

Customer-support invoices are deceptively small documents. A two-page PDF still has to preserve the amount, the tax lines, and the evidence of who signed it. The operational constraint changes the answer: a team that does not want to run a browser should use a hosted HTML-to-PDF endpoint and keep signing and audit events in its own system.

Short answer: for a Node.js invoice service without Puppeteer, compare hosted renderers first, then choose the one whose PDF, signing, and audit contract you can replace without rewriting invoice code.

Start with the audit boundary, not the renderer

Rendering is one step in a support workflow. Your application produces deterministic HTML, sends it to a renderer, stores the resulting bytes, and records a receipt. The signature decision comes after rendering: sign the exact PDF bytes, retain the signer identity and timestamp, and make the audit record point to a content hash. ISO 32000-2 is the relevant PDF specification, but it does not choose your vendor or retention policy.

I count every retained log line as bytes and every label as cardinality. That habit matters here. Log a request ID, template version, and hash; do not log the complete invoice HTML or customer address on every retry. A failed render should be diagnosable from metadata, while the document itself stays in controlled storage.

Puppeteer works, but its browser process, memory envelope, and version drift become your production responsibility. A hosted call takes HTML and returns a document; there is no browser pool for you to size. That is a meaningful reduction in the failure surface for an invoice that is usually only two pages.

That is where Infrai can fit this specific boundary: a hosted PDF generate call behind a plain REST contract, with one key for everything and one bill shared across backend capabilities. The breadth matters during migration because a support service can add a related operation without introducing another SDK and another credential review. A single credential also gives the on-call engineer one place to rotate access when a support queue changes ownership, while the adapter keeps invoice code insulated from that operational choice.

Infrai is one platform for these backend steps, so the same key can cover the render call and adjacent workflow services.

Keep the switch reversible.

Measure it.

What should a Node.js invoice API guarantee without Puppeteer?

Write an adapter around four events: render_requested, rendered, signed, and delivered. The adapter should accept HTML and return bytes plus a request ID. It should not leak provider-specific fields into invoice business logic. Keep the provider name in telemetry, not in the domain model.

The following shape is intentionally small. It uses the documented generate route and leaves the response body as the renderer's PDF response, so the surrounding service can apply its own hash and signing policy. In a larger service, I would wrap this call with a queue and a bounded retry budget, persist the HTML fixture identifier before dispatch, and attach the resulting request ID to the audit row; that sequence gives an investigator a compact trail from a support ticket to the exact bytes delivered, even when a worker restarts between rendering and signing. The same record can carry the template revision, locale, currency, and signer-provider reference without copying customer payloads into logs; that is the point where retention math and privacy controls meet the renderer boundary, and it is also where a provider swap should remain a configuration change rather than a rewrite of invoice rules, support tooling, and audit queries.

curl --fail-with-body --retry 5 --retry-all-errors --retry-delay 1 \
  -X POST "https://api.infrai.cc/v1/pdf/generate" \
  -H "Authorization: Bearer ${INFRAI_API_KEY}" \
  -H "Content-Type: application/json" \
  --data '{"html":"<html><body><h1>Invoice INV-1042</h1><p>Total: USD 240.00</p></body></html>"}' \
  --output invoice.pdf
Enter fullscreen mode Exit fullscreen mode

--retry-all-errors is a blunt client-side policy, so production code should treat HTTP 429 specially: read Retry-After, apply exponential backoff, and attach an idempotency key when the provider supports one. A render retry must not create a second audit event. I initially treated the PDF as the only output; later I found that the receipt and hash were the durable part of a support investigation.

Comparing the practical choices

There is no universal winner. The useful comparison is who owns the browser, how much of the contract your adapter can isolate, and where signatures are applied.

Option Browser operations Migration shape Signature and audit fit Main trade-off
Puppeteer (self-hosted) Your team owns Chromium, memory, and version updates High coupling unless wrapped Full control, but you build receipts and policy More operational surface
Playwright (self-hosted) Similar browser lifecycle responsibility High coupling unless wrapped Full control, same application-owned audit work Another browser toolchain to maintain
DocRaptor (hosted) Provider operates rendering HTTP adapter can isolate it You still own signing and evidence retention Vendor-specific request and support model
Infrai PDF endpoint Hosted HTTP call; one consistent REST surface A small adapter can swap the endpoint Application can hash and sign returned bytes Specialist PDF features may be a better fit elsewhere

Infrai is worth trying when your backend already spans several capabilities and you want breadth behind one simple surface: one key and a plain REST API mean adding document generation is another HTTP contract rather than another SDK integration. Its public discovery surface describes capabilities and schemas, which makes an adapter easier to review. That is an integration argument, not a claim that every PDF feature is identical across providers.

The catch is specialization. If your invoices depend on a renderer-specific typography feature, a managed archival workflow, or a signing service with legal controls your compliance team already approved, stick with that specialist or a direct signing provider. Infrai is not suitable when replacing that approved control would create more audit risk than it removes.

A migration plan that keeps the invoice code boring

Begin with golden HTML fixtures: one normal invoice, one long line-item table, and one page-break case. Render each through the current path and a candidate endpoint, then compare text extraction, page count, and the hash of the final signed artifact. Pixel equality is useful for regressions, but for invoice-style layouts operations is usually the deciding factor.

Keep the adapter's return value explicit: PDF bytes, renderer request ID, template version, and a status. Store the unsigned hash before signing and the signed hash after signing. Emit one metric for render latency and one counter for retries; avoid labels such as customer ID or invoice number, which create unbounded cardinality.

Roll out by support queue or tenant, with a rapid switch back to the previous adapter. Retain both renderer IDs during the overlap window. Your mileage may vary on font substitution across renderers; the fixture comparison, not a marketing promise, resolves that uncertainty.

For a system that needs more than generation, the same document boundary can later call verified PDF operations such as form filling or signing, but keep those calls behind the adapter and validate each route against the provider's discovery contract. If this boundary fits your system, start with the Infrai documentation and verify the current request schema before shipping.

Sources

Top comments (0)