Générer un PDF depuis HTML et JSON avec Node.js

Ce guide transforme les données d’une facture fictive en PDF avec Node.js. Le modèle reste enregistré dans Templatr ; votre serveur transmet les données à chaque génération. Vous obtenez un fichier binaire à enregistrer ou à servir depuis votre application, sans installer Chromium sur votre serveur.

PDF statique de démonstration, produit avec les fichiers ci-dessous et Chromium. Les données sont fictives.

Prérequis

  • Node.js 22 ou ultérieur. Le script utilise fetch et les modules intégrés, sans paquet supplémentaire.
  • Un compte Templatr, une clé API et du quota PDF disponible.
  • Un terminal avec curl pour envoyer le modèle une première fois. Les commandes d’environnement ci-dessous utilisent un shell POSIX.

1. Préparer un modèle HTML réutilisable

Téléchargez ce fichier sous le nom invoice-template.html. Le modèle utilise des propriétés imbriquées et une boucle items. Dans cette boucle, item représente chaque objet du tableau items ; les valeurs sont échappées en HTML. Gardez les styles dans le modèle : JavaScript est désactivé pendant le rendu.

html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Invoice {{invoice.number}}</title>
  <style>
    @page { size: A4; margin: 18mm; }
    body { font: 11pt Arial, sans-serif; color: #172033; }
    h1 { font-size: 26pt; margin-bottom: 8mm; }
    table { width: 100%; border-collapse: collapse; margin: 8mm 0; }
    th, td { padding: 3mm; border-bottom: 1px solid #dbe2ea; text-align: left; }
    th:last-child, td:last-child { text-align: right; }
    thead { display: table-header-group; }
    tr, .total { break-inside: avoid; }
    .total { text-align: right; font-weight: bold; }
  </style>
</head>
<body>
  <h1>Invoice {{invoice.number}}</h1>
  <p>From: {{seller.name}}</p>
  <p>Bill to: {{customer.name}}</p>
  <p>Date: {{invoice.date}}</p>
  <table>
    <thead><tr><th>Description</th><th>Quantity</th><th>Amount</th></tr></thead>
    <tbody>{{ items loop }}<tr><td>{{item.description}}</td><td>{{item.quantity}}</td><td>{{item.amount}}</td></tr>{{ end loop }}</tbody>
  </table>
  <p class="total">Total: {{invoice.total}}</p>
  <p>Sample document with fictional data. No payment is due.</p>
</body>
</html>
Télécharger invoice-template.html

2. Préparer les valeurs JSON

Téléchargez invoice-data.json dans le même dossier. L’objet JSON constitue directement le corps de la requête, sans enveloppe data supplémentaire. Formatez dates, devises et totaux dans votre application. Templatr insère les valeurs à afficher ; il ne calcule pas le total à partir des lignes.

json
{
  "seller": { "name": "Acme Studio" },
  "customer": { "name": "Northstar Co." },
  "invoice": { "number": "DEMO-0042", "date": "2026-09-23", "total": "EUR 299.00" },
  "items": [
    { "description": "Template design", "quantity": 1, "amount": "EUR 199.00" },
    { "description": "Integration review", "quantity": 2, "amount": "EUR 100.00" }
  ]
}
Télécharger invoice-data.json

3. Envoyer le modèle et conserver son identifiant

Créez une clé API dans les paramètres, puis définissez TEMPLATR_API_KEY dans l’environnement de votre serveur. Exécutez cette commande depuis le dossier du modèle. La réponse JSON contient template_id. Affectez cette valeur à TEMPLATR_TEMPLATE_ID et conservez-la : il est inutile d’envoyer le même HTML pour chaque document.

bash
export TEMPLATR_API_KEY='YOUR_API_KEY'
curl --fail-with-body "https://api.templatr.app/upload" \
  -H "Authorization: Bearer $TEMPLATR_API_KEY" \
  -F "[email protected]" \
  -F "name=Example invoice"
export TEMPLATR_TEMPLATE_ID='TEMPLATE_ID_FROM_RESPONSE'

4. Générer et enregistrer le PDF

Enregistrez le script ci-dessous à côté de invoice-data.json. Il envoie le JSON à la route de téléchargement PDF, vérifie le statut HTTP et le type de contenu, puis écrit invoice.pdf uniquement si la réponse commence par un en-tête PDF. Conservez la clé dans les variables d’environnement du serveur, jamais dans le code du navigateur ni dans un dépôt public.

L’extension .mjs permet les imports et await sans configuration package.json. Le délai maximal du script est de 60 secondes.

javascript
import { readFile, writeFile } from "node:fs/promises";

const apiKey = process.env.TEMPLATR_API_KEY;
const templateId = process.env.TEMPLATR_TEMPLATE_ID;
if (!apiKey || !templateId) throw new Error("Set TEMPLATR_API_KEY and TEMPLATR_TEMPLATE_ID first.");

const data = JSON.parse(await readFile(process.argv[2] || "invoice-data.json", "utf8"));
const baseUrl = process.env.TEMPLATR_API_BASE_URL || "https://api.templatr.app";
const response = await fetch(`${baseUrl}/pdf/${encodeURIComponent(templateId)}?format=pdf`, {
  method: "POST",
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
  body: JSON.stringify(data),
  signal: AbortSignal.timeout(60_000),
});
if (!response.ok) throw new Error(`PDF request failed (HTTP ${response.status}). Check the API error guide.`);
if (!response.headers.get("content-type")?.includes("application/pdf")) {
  throw new Error("Expected an application/pdf response.");
}
const pdf = Buffer.from(await response.arrayBuffer());
if (pdf.subarray(0, 5).toString() !== "%PDF-") throw new Error("Response does not contain a PDF.");
await writeFile(process.argv[3] || "invoice.pdf", pdf);
console.log("PDF saved.");
Télécharger generate-pdf.mjs

5. Exécuter le script

Le premier argument est le fichier JSON, le second est le fichier PDF de sortie. Les variables d’environnement doivent être définies dans le même terminal.

bash
node generate-pdf.mjs invoice-data.json invoice.pdf

Vérifications et erreurs

  • Ouvrez invoice.pdf et vérifiez DEMO-0042, Northstar Co., les deux lignes et le total de EUR 299.00. Le document d’exemple est en anglais.
  • Changez le nom du client et une description dans invoice-data.json, relancez le script et vérifiez les nouvelles valeurs.
  • Testez les noms et descriptions longs avant d’utiliser vos données réelles. Une variable absente produit une chaîne vide.
  • Pour une erreur 401, vérifiez la clé API. Pour 404, vérifiez que le modèle appartient au compte de la clé. Pour 429, contrôlez le quota du compte et l’en-tête Retry-After.
  • Un délai dépassé ne prouve pas l’échec de la génération côté serveur. Vérifiez l’historique avant de réessayer : plusieurs requêtes réussies peuvent consommer plusieurs crédits.
Consulter les quotas et limites de l’API

Pour continuer