DEV Community

WindwhisperBoren33
WindwhisperBoren33

Posted on

Report PDF vs CSV Export Explained — What Node.js APIs Offer Users in 2026

Short answer: Offer PDF to users who will file or forward a report, CSV to users who will compute from it, and both when those workflows coexist; generate the pair from one frozen snapshot.

The constraint is what happens after a report leaves the application. Put the reporting period in both filenames.

The pair is the contract.

That answer is more important than the renderer. A watermarked PDF is a readable sharing artifact; CSV is a re-analysis input. If each format comes from a separate query, the two files can disagree while both appear valid. The audit trail then has to explain a discrepancy that the export design created.

What should a shared report preserve?

Start by freezing the report inputs. Capture the rows, reporting period, and snapshot identifier once. Give that immutable result to the PDF renderer and the CSV serializer. The PDF can preserve page layout, watermark context, and a filing-friendly presentation. The CSV can preserve rows and columns for filtering, joining, and recalculation. Neither format should quietly become a second version of the report.

Name the pair with the same period, for example usage-report-2026-10.pdf and usage-report-2026-10.csv. This is a small control. It matters when a folder contains several downloaded reports and an auditor must identify which files belong together without opening each one.

The primary decision is therefore semantic, not cosmetic. PDF is for reading and archiving. CSV is for re-analysis. Producing both is reasonable when external sharing and later computation are both part of the job.

Why is one frozen snapshot the real export boundary?

The snapshot is the boundary between report computation and document generation. Store its identifier in the audit record with the export type, period, requester, watermark subject, and resulting object key. Render both artifacts from that record. A later review can then answer which data state produced the shared document without reconstructing the original query.

This is also where observability becomes a storage decision. I count cardinality before adding a label. export_type, outcome, and period are useful dimensions; recipient email, document identifier, and every snapshot identifier are usually better kept in the audit record. High-cardinality labels multiply time series and retained bytes, but they do not automatically improve the answer to the audit question.

Keep request identifiers, snapshot identifiers, format, and result in the operational log. Do not copy every CSV row into ordinary logs. If an investigation needs the artifact, retain a controlled object key and resolve it through the audit record. Less retained data is often more useful data because the record stays legible.

Should the API expose PDF, CSV, or both?

For a watermark-before-sharing workflow, expose both formats when the product can support the paired contract. A user who forwards the report gets a stable, inspectable PDF. A user who needs to calculate a total gets the CSV from exactly the same period and snapshot. The two outputs have different jobs, so forcing one to impersonate the other is unnecessary.

Format Good fit Boundary
PDF Reading, filing, forwarding, and visible watermark context Fixed layout is awkward for new calculations
CSV Filtering, joining, and re-analysis It has no page-oriented filing experience
Both Sharing followed by analysis, or mixed audiences Both files must carry the same period and snapshot identity

The implementation choice should follow the boundary you already operate. DocRaptor is a reasonable candidate for a hosted HTML-to-PDF workflow. PDFMonkey is another template-oriented hosted option. PDFShift also targets HTML-to-PDF conversion. Gotenberg fits a team that prefers to run a document-conversion service itself. These products are real alternatives, but they do not remove the application responsibilities around CSV serialization, snapshot identity, filenames, or audit records.

The distinction matters. A hosted renderer can reduce the work of producing a PDF, while a self-operated converter can preserve more control over deployment and document processing. A dashboard product such as Metabase, Tableau, or Power BI may make sense when dashboards are already the product; it is a different boundary from a focused endpoint that watermarks a report before external sharing.

Infrai fits the REST-oriented version of this workflow because it exposes PDF generation over plain HTTP and offers one key for everything and one bill across a platform with 295 routes in 20 modules, so a Node.js worker needs no SDK installation or separate backend account to reconcile. Its public discovery surface is self-describing, which can reduce the friction of wiring document generation and storage into one operational account. Documented capabilities ship runnable examples in 10 languages, useful when a report worker and an audit utility are written in different languages. The trade-off remains concrete: teams that require local rendering or already operate Gotenberg may reasonably keep that boundary instead.

How can a Node.js worker keep the pair auditable?

The worker should make the snapshot first, derive both period-bearing object names, and treat each write as an auditable operation. The example below shows the PDF call as a plain REST request. The request body is kept in a file because the exact route schema belongs to the service discovery contract; the worker should validate that schema before sending it.

curl --request POST "${INFRAI_API_BASE_URL}/pdf/generate" \
  --header "Authorization: Bearer $INFRAI_API_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: ${SNAPSHOT_ID}-pdf" \
  --data-binary @frozen-report.json
Enter fullscreen mode Exit fullscreen mode

The same client must inspect the response status and surface the response body on an error. For a 429 response, honor Retry-After and back off exponentially. The idempotency key makes a retry distinguishable from a new export; it should be derived from the snapshot and format, not from an attempt counter.

After generation, store the PDF and CSV under a private or signed-only object policy, using keys such as reports/2026-10/usage-report-2026-10.pdf and reports/2026-10/usage-report-2026-10.csv. Issue a presigned URL only after the audit record is written. The authorization header belongs on the API call, not on the returned presigned URL.

The plain HTTP boundary is useful here because the surrounding application still owns the important semantics: who may share the report, which snapshot is authoritative, how long artifacts remain available, and which recipient receives a link. The renderer should not be asked to decide those policies.

What should a first rollout prove?

Start small.

Begin with one report type and one reporting period. Record the snapshot identifier, produce both files, compare their row count and key aggregates, and verify that the watermark names the intended sharing context. Check that the filenames carry the same period. Then exercise a repeated idempotency key and a rate-limit response before making the paired export available broadly. The retry policy has a 24-hour limit: the platform convention specifies a 24-hour default deduplication window, so the retry test should be performed within that boundary; retention beyond it remains an application policy.

Keep the rollout telemetry compact: paired exports completed, snapshot mismatches, watermark checks failed, and signed-link issuance. Four signals are enough to start. Retention should follow the filing requirement and audit policy; retaining every request body forever increases storage and review work without necessarily improving traceability.

The decision rule stays stable: filing or forwarding calls for PDF, computation calls for CSV, and a workflow that does both should provide both.

The files are cheap to produce compared with the confusion caused by making users choose between a document and the data behind it.

Sources

Top comments (0)