node-print

Generazione e stampa di documenti.
document-output
/printauth: nginx

node-print

In sintesi

Motore di stampa headless del tenant ditta: trasforma HTML arbitrario in documenti PDF o immagini raster (PNG, JPEG, WebP) tramite un pool di istanze Chromium pilotate da Puppeteer. Espone API sincrone per generazioni leggere, API batch per fan-out concorrenti, API asincrone con job tracking e progress in SSE per stampe lunghe, e una destinazione webhook per consegnare l'artefatto a un sistema terzo. Le sue uscite tipiche sono fatture, bolle di consegna, etichette, report e ricevute, che a monte vengono assemblate come HTML da node-renderer e a valle vengono archiviate su node-storage o trasmesse via webhook.

Funzionalità principali

  • Generazione PDF da HTML con controllo di formato (A4, A5, Letter, …), orientamento, margini, header/footer template, sfondo e CSS print
  • Generazione immagini (PNG, JPEG, WebP) con viewport configurabile, qualità, ritaglio e full-page screenshot — pensata per etichette, badge e anteprime
  • Modalità output triplici: stream binario (generate-pdf), payload Base64 incapsulato in JSON (generate-pdf-base64), oppure consegna webhook in formato multipart/form-data o JSON con buffer base64
  • Batch processing con concurrency manager: una singola richiesta accetta N pagine HTML e restituisce un manifest dei file generati, con stato per item e gestione del fallimento parziale
  • Job asincroni con coda interna, rate limiting per utente, retry configurabili, statistiche aggregate e notifiche SSE/WebSocket di avanzamento (/jobs/:id/status, /jobs/statistics)
  • Template validation e HTML toolkit: endpoint dedicati per preview, minify, clean, analisi e ottimizzazione print del markup prima della stampa
  • Browser pool: istanze Chromium riutilizzate, contesto isolato per richiesta, timeout waitUntil configurabile (domcontentloaded di default, networkidle0 per template con asset esterni)
  • Integrazione orchestrator: lo stesso servizio si registra come modulo verso node-orchestrator esponendo i propri task come step di workflow

Architettura

Stack: Fastify v5 - Puppeteer (Chromium headless) - Inversify (DI con constructor injection) - @pzeta/fastify-utils (security, errorHandler, healthcheck, metrics, openapi, tracing, logging) - @pzeta/log per logging strutturato - @pzeta/orchestrator-node (ModuleSDK) per registrazione come modulo di workflow - axios + form-data per la consegna webhook - Zod per la validazione di request e response.

Layout DDD (src/):

LayerContenuto
domain/DocumentGenerationDomainService, DocumentRenderingService, HtmlOptimizationService, port IBrowserEnginePort, value object (JobResult, WebhookConfig, …), errori di dominio
application/Facade DocumentGenerationService + servizi specializzati (DocumentGenerationApplicationService, DocumentMetricsApplicationService), BatchGenerationService con BatchConcurrencyManager, AsyncJobOrchestrationService + AsyncJobStorage, TemplateValidationService, HtmlOptimizationApplicationService, WebhookDeliveryService
infrastructure/PuppeteerBrowserAdapter (implementazione di IBrowserEnginePort), BrowserPool, BrowserPageWrapper, OrchestratorAdapter con handler modulari (CoreHandlers, BatchHandlers, AsyncHandlers, TemplateHandlers, HtmlHandlers, ConfigHandlers), ServiceFactory (composition root Inversify), ConfigManagerAdapter
presentation/DocumentsRoutes (sync), BatchRoutes, AsyncRoutes, TemplateRoutes, HtmlOptimizationRoutes, ConfigurationRoutes, controller DocumentGeneratorController, schemi Zod request/response

Pattern adottati: Facade su DocumentGenerationService, Adapter sul browser engine, Object Pool sulle istanze Chromium, Strategy sulla WebhookDeliveryService (multipart vs JSON+Base64), Factory in ServiceFactory, Coordinator + Direct SDK Integration nell'OrchestratorAdapter.

Modello di rendering: il servizio è puramente HTML-driven, non interpreta DSL proprietarie. La separazione di competenze nel cluster è:

  1. node-renderer riceve un template + dati e produce un blob HTML
  2. il caller (frontend, orchestrator, scheduler) gira quell'HTML a node-print
  3. node-print lancia una page Chromium, ne ottiene il PDF/immagine e lo restituisce inline, in webhook, o lo lascia raccogliere dal job storage
  4. il caller archivia poi l'artefatto su node-storage (o lo offre in download al browser)

Non esiste una chiamata diretta node-print → node-storage: il dipendency node-storage nel frontmatter modella il flusso end-to-end tipico, non un'invocazione HTTP del servizio.

Casi d'uso

  • Stampa fattura singola: il frontend Vue genera l'HTML della fattura tramite node-renderer e invia POST /print/generate-pdf ricevendo il PDF in streaming, pronto al download
  • Etichetta termica capi: l'app gestionale chiede POST /print/generate-image con viewport ridotto e formato PNG per produrre etichette da inviare a stampanti di rete via driver lato client
  • Batch documenti di chiusura giornata: a fine turno il backend chiama POST /print/batch/pdfs con N ricevute HTML; il servizio le genera in parallelo limitando la concorrenza, ritorna il batchId e l'esito di ciascun item via GET /print/batch/:id/status
  • Stampa massiva da workflow: node-orchestrator esegue un workflow di chiusura mese che, per ogni cliente, invoca i task generate-pdf o generate-pdf-base64 di node-print registrati come modulo; il workflow inoltra poi gli output a node-storage per archiviazione fiscale
  • Generazione asincrona con notifica esterna: POST /print/async/generate-pdf accoda un job con destination: webhook; al termine il WebhookDeliveryService consegna il PDF (multipart o JSON+base64) all'URL del consumer, che riceve la notifica senza polling
  • Validazione preventiva del template: prima di stampare in produzione, l'integratore chiama /print/templates/validate-quick o /print/html/analysis per individuare problemi (asset mancanti, CSS non print-safe, dimensioni eccessive)

Identità & esposizione

CampoValore
Categoriadocument-output
Versione cluster1.0.0
Imagegitea.pzetatouch.it/pzeta_touch/node-print:1.0.28
URL pubblicohttps://ditta.pzeta.it/print
Path regex ingress`/print(/
Rewrite a backend/$2
DNS internonode-print-ditta.ditta.svc.cluster.local:3000
Auth nginxauth_requestnode-user-auth
Repositorynode-print
Endpoint REST29 (vedi sezione "API reference")

Endpoint operazionali

Endpoint convenzionali esposti da tutti i microservizi PZeta basati su @pzeta/fastify-utils:

Path pubblicoScopo
https://ditta.pzeta.it/print/healthliveness probe
https://ditta.pzeta.it/print/readyreadiness probe
https://ditta.pzeta.it/print/metricsmetriche Prometheus
https://ditta.pzeta.it/print/api-docs.jsonspec OpenAPI runtime (richiede OPENAPI_EXPOSE_IN_PRODUCTION=true)
https://ditta.pzeta.it/print/api-docsSwagger UI (solo in NODE_ENV !== production)

Configurazione

Variabili d'ambiente che un integratore deve conoscere (la lista completa è in src/infrastructure/config/schema.ts del repo):

VariabileRuolo
HOST / PORTBind di ascolto del processo Fastify (porta interna 3000)
REVERSE_PROXYA true quando il servizio è dietro nginx, per la corretta gestione di X-Forwarded-*
ENABLE_CORS / CORS_ALLOWED_ORIGINSPolitica CORS in produzione
PUPPETEER_EXECUTABLE_PATHOverride del binario Chromium (utile in container)
PUPPETEER_TIMEOUTTimeout massimo di rendering di una page
PUPPETEER_ARGSFlag aggiuntivi del browser (es. --no-sandbox in container)
BROWSER_WAIT_UNTILStrategia di attesa caricamento page: domcontentloaded (default, più veloce), networkidle0 (richiesto se il template carica asset esterni)
BASE64_MAX_FILE_SIZE / BASE64_MEMORY_THRESHOLDTetti di memoria per la modalità Base64; oltre il threshold il servizio rifiuta il job per non saturare l'heap
WEBHOOK_MAX_BODY_BYTESDimensione massima del payload webhook recapitato (default 50 MB)
ASYNC_JOBS_ENABLED / ASYNC_JOBS_MAX_CONCURRENT / ASYNC_JOBS_DEFAULT_TTLAbilita la coda asincrona e ne limita concorrenza e durata
ASYNC_JOBS_MAX_RETRIES / ASYNC_JOBS_RETRY_DELAYPolitica di retry sui job falliti
ASYNC_JOBS_RATE_LIMIT_WINDOW / ASYNC_JOBS_RATE_LIMIT_MAXRate limit per utente sull'accodamento di nuovi job
ASYNC_JOBS_NOTIFICATION_TYPECanale di notifica progress: sse (default) o websocket
ORCHESTRATOR_ENABLED / ORCHESTRATOR_URL / ORCHESTRATOR_API_KEYSe true, registra node-print come modulo presso node-orchestrator esponendo i task di stampa come step di workflow
MODULE_ID / MODULE_NAME / MODULE_VERSIONOverride del manifest pubblicato all'orchestrator (default presi da package.json)
ENABLE_METRICSPubblica le metriche Prometheus su /metrics
LOG_LEVELLivello di logging (debug / info / warn / error)

Swagger UI è abilitato solo in NODE_ENV !== production. Lo spec OpenAPI runtime su /api-docs.json è esposto solo se OPENAPI_EXPOSE_IN_PRODUCTION=true.

Note eventing NATS

node-print non si sottoscrive a NATS in modo diretto: lo scanner statico riportato in nats.json non rileva publish o subscribe propri del servizio. La comunicazione asincrona avviene su due canali distinti, entrambi gestiti fuori dal codice del servizio:

  1. Modulo dell'orchestrator: quando ORCHESTRATOR_ENABLED=true, l'OrchestratorAdapter usa @pzeta/orchestrator-node (ModuleSDK) per registrarsi presso node-orchestrator. Le invocazioni dei task (generate-pdf, generate-image, generate-pdf-base64, generate-image-base64, batch, async, template, html, config) viaggiano su subject NATS interni all'SDK, derivati dal manifest del modulo, con request/reply tipizzati. Da fuori il servizio appare quindi come consumer di task dell'orchestrator, non come publisher di eventi di dominio.
  2. Notifiche progress dei job asincroni: il canale è SSE (o WebSocket, selezionabile via ASYNC_JOBS_NOTIFICATION_TYPE), non NATS. Il client si sottoscrive a /jobs/:id/status per ricevere gli aggiornamenti incrementali.

Per consegnare l'artefatto a sistemi esterni node-print usa il WebhookDeliveryService su HTTP — anch'esso non passa per NATS. La tabella subject auto-renderizzata sotto sarà quindi vuota: è il comportamento atteso per questo servizio.

Dipendenze e dipendenti

Dipende da (servizi che questo servizio chiama):

Consumato da (chi chiama questo servizio):

Infrastruttura (PostgreSQL, NATS, Redis, MinIO) non è elencata qui — vedi sezione Architettura del singolo servizio.

Loading OpenAPI…
Loading NATS contracts…