No headless browser
Native bindings compile PDFs inside your service. No Chromium download in your Docker image, no browser pool to babysit.
The default answer in Node.js is Puppeteer: render HTML in headless Chrome and print it to PDF. That means shipping a full browser with your service, hundreds of megabytes of memory, slow cold starts, and CSS that was never designed for paged documents.
Oicana compiles PDFs in process through native bindings instead. Install with npm install @oicana/node, load a packed template once, and compile documents from JSON:
import { readFile } from 'node:fs/promises';import { Template } from '@oicana/node';
const template = new Template(await readFile('invoice-0.1.0.zip'));
const jsonInputs = new Map<string, string>();jsonInputs.set('invoice', JSON.stringify({ number: '2026-001', customer: 'Acme GmbH', total: '€1,190.00',}));
const pdf = template.export(jsonInputs, new Map());A warmed up template produces a print-ready PDF in single digit milliseconds, directly in your service.
The snippet loads invoice-0.1.0.zip, a packed Oicana template. Templates are plain Typst projects: a typst.toml manifest declares a JSON input named invoice, and main.typ uses it:
#import "@preview/oicana:0.2.0": setup
#let read-project-file(path) = read(path, encoding: none)#let (input, oicana-image, oicana-config) = setup(read-project-file)
#set document(date: datetime.today())
= Invoice #input.invoice.number
Billed to: #input.invoice.customer
*Total: #input.invoice.total*Running oicana pack in the template directory produces the zip. Create a Template walks through the manifest and input definitions, and the open source example templates include a complete invoice.
Load the template once at service startup and compile per request; after that there is no file I/O on the hot path. Compilation is CPU-bound, so every method has an async variant (exportPdfAsync, compileAsync, and friends) that runs on Node.js’ libuv thread pool and keeps the event loop free under load. The async Node.js compilation guide shows the async methods and how to size the thread pool.
The NestJS example project shows a complete service, including blob inputs, error handling, and Swagger documentation.
No headless browser
Native bindings compile PDFs inside your service. No Chromium download in your Docker image, no browser pool to babysit.
Your data stays with you
Documents are generated inside your own service. No customer data is sent to a third party, which keeps GDPR and HIPAA compliance in your hands.
Flat price, unlimited documents
One flat license per application, no metering. A hundred invoices a month cost the same as a hundred thousand. Non-commercial use is free.