Latex Node
LaTeX with Node.js: compile LaTeX documents programmatically, template-based PDF generation, mathematical typesetting, academic papers
You are an expert in using LaTeX from Node.js for high-quality document generation.
## Key Points
- Always escape user-supplied text with a dedicated function before interpolating it into LaTeX source to prevent compilation errors and injection of arbitrary LaTeX commands.
- Use `xelatex` or `lualatex` when you need Unicode characters or custom OpenType fonts; reserve `pdflatex` for simpler ASCII-only documents where speed matters.
- Run at least two compilation passes when the document uses `\tableofcontents`, `\ref`, `\cite`, or any cross-referencing — the first pass collects labels and the second resolves them.
- Not escaping special characters (`&`, `%`, `$`, `#`, `_`, `{`, `}`, `\`, `~`, `^`) in dynamic content — a single unescaped ampersand will crash the entire compilation with a cryptic error.skilldb get document-generation-services-skills/Latex NodeFull skill: 312 linesLaTeX with Node.js — Document Generation
You are an expert in using LaTeX from Node.js for high-quality document generation.
Core Philosophy
Overview
LaTeX is the gold standard for typesetting academic papers, technical reports, and documents with complex mathematical notation. By driving LaTeX from Node.js you can template documents programmatically, compile them to PDF, and integrate the workflow into web services or CI pipelines. This approach is ideal when you need publication-quality output with precise control over typography, equations, bibliographies, and cross-references that no HTML-to-PDF engine can match.
Setup & Configuration
# Install a LaTeX distribution (required on the host)
# Ubuntu/Debian:
sudo apt-get install texlive-full latexmk
# macOS:
brew install --cask mactex
# Windows: install MiKTeX from https://miktex.org/download
# Node.js dependencies
npm install node-latex tmp-promise
npm install -D @types/tmp
// latex-compiler.ts
import { exec } from "child_process";
import { promisify } from "util";
import { readFile, writeFile, mkdir } from "fs/promises";
import { join } from "path";
import { dir as tmpDir } from "tmp-promise";
const execAsync = promisify(exec);
interface CompileOptions {
/** Number of compilation passes (needed for references/TOC) */
passes?: number;
/** LaTeX engine: pdflatex, xelatex, or lualatex */
engine?: "pdflatex" | "xelatex" | "lualatex";
/** Additional files to copy into the build directory */
assets?: Array<{ name: string; content: Buffer }>;
/** BibTeX file content for bibliography */
bibContent?: string;
}
async function compileLatex(
texSource: string,
options: CompileOptions = {}
): Promise<Buffer> {
const { passes = 2, engine = "pdflatex", assets = [], bibContent } = options;
const tmpResult = await tmpDir({ unsafeCleanup: true });
const workDir = tmpResult.path;
const texFile = join(workDir, "document.tex");
const pdfFile = join(workDir, "document.pdf");
try {
await writeFile(texFile, texSource, "utf-8");
// Copy any assets (images, style files) into the work directory
for (const asset of assets) {
await writeFile(join(workDir, asset.name), asset.content);
}
// Write bibliography if provided
if (bibContent) {
await writeFile(join(workDir, "references.bib"), bibContent, "utf-8");
}
// Run the LaTeX engine the required number of passes
for (let i = 0; i < passes; i++) {
await execAsync(
`${engine} -interaction=nonstopmode -output-directory="${workDir}" "${texFile}"`,
{ cwd: workDir, timeout: 60_000 }
);
// Run bibtex after the first pass if we have a bibliography
if (i === 0 && bibContent) {
await execAsync(`bibtex document`, { cwd: workDir, timeout: 30_000 });
}
}
return await readFile(pdfFile);
} catch (error: any) {
// Read the log file for diagnostics
const logFile = join(workDir, "document.log");
try {
const log = await readFile(logFile, "utf-8");
const errorLines = log
.split("\n")
.filter((l) => l.startsWith("!") || l.includes("Error"))
.slice(0, 10);
throw new Error(
`LaTeX compilation failed:\n${errorLines.join("\n")}\n\nOriginal: ${error.message}`
);
} catch {
throw error;
}
} finally {
await tmpResult.cleanup();
}
}
export { compileLatex, CompileOptions };
Core Patterns
Template-based document generation
// template-engine.ts
/** Escape special LaTeX characters in user-provided strings */
function escapeLatex(text: string): string {
const replacements: Record<string, string> = {
"\\": "\\textbackslash{}",
"{": "\\{",
"}": "\\}",
"&": "\\&",
"%": "\\%",
$: "\\$",
"#": "\\#",
_: "\\_",
"~": "\\textasciitilde{}",
"^": "\\textasciicircum{}",
};
return text.replace(/[\\{}&%$#_~^]/g, (ch) => replacements[ch]);
}
interface InvoiceItem {
description: string;
quantity: number;
unitPrice: number;
}
interface InvoiceData {
invoiceNumber: string;
date: string;
clientName: string;
clientAddress: string;
items: InvoiceItem[];
notes?: string;
}
function generateInvoiceLatex(data: InvoiceData): string {
const itemRows = data.items
.map(
(item) =>
`${escapeLatex(item.description)} & ${item.quantity} & \\$${item.unitPrice.toFixed(2)} & \\$${(item.quantity * item.unitPrice).toFixed(2)} \\\\`
)
.join("\n \\hline\n ");
const total = data.items.reduce(
(sum, item) => sum + item.quantity * item.unitPrice,
0
);
return `
\\documentclass[11pt,a4paper]{article}
\\usepackage[margin=2cm]{geometry}
\\usepackage{booktabs}
\\usepackage{array}
\\usepackage{graphicx}
\\usepackage{xcolor}
\\usepackage{fancyhdr}
\\definecolor{primary}{HTML}{2563EB}
\\pagestyle{fancy}
\\fancyhf{}
\\rhead{Invoice \\#${escapeLatex(data.invoiceNumber)}}
\\rfoot{Page \\thepage}
\\begin{document}
\\begin{flushright}
{\\Large\\bfseries\\color{primary} INVOICE}\\\\[4pt]
\\#${escapeLatex(data.invoiceNumber)}\\\\
${escapeLatex(data.date)}
\\end{flushright}
\\vspace{1cm}
\\textbf{Bill To:}\\\\
${escapeLatex(data.clientName)}\\\\
${escapeLatex(data.clientAddress)}
\\vspace{1cm}
\\begin{tabular}{|p{7cm}|c|r|r|}
\\hline
\\textbf{Description} & \\textbf{Qty} & \\textbf{Unit Price} & \\textbf{Total} \\\\
\\hline
${itemRows}
\\hline
\\multicolumn{3}{|r|}{\\textbf{Grand Total}} & \\textbf{\\$${total.toFixed(2)}} \\\\
\\hline
\\end{tabular}
${data.notes ? `\\vspace{1cm}\n\\textbf{Notes:} ${escapeLatex(data.notes)}` : ""}
\\end{document}
`;
}
// Usage
async function generateInvoicePdf(data: InvoiceData): Promise<Buffer> {
const tex = generateInvoiceLatex(data);
return compileLatex(tex, { engine: "pdflatex", passes: 1 });
}
Mathematical documents with XeLaTeX and custom fonts
function generateMathReport(title: string, sections: Array<{ heading: string; content: string; equations: string[] }>): string {
const body = sections
.map(
(s) => `
\\section{${escapeLatex(s.heading)}}
${s.content}
${s.equations.map((eq) => `\\begin{equation}\n ${eq}\n\\end{equation}`).join("\n")}`
)
.join("\n");
return `
\\documentclass[12pt]{article}
\\usepackage{fontspec}
\\setmainfont{Latin Modern Roman}
\\usepackage{amsmath,amssymb,amsthm}
\\usepackage{hyperref}
\\usepackage[margin=2.5cm]{geometry}
\\title{${escapeLatex(title)}}
\\author{Generated Report}
\\date{\\today}
\\newtheorem{theorem}{Theorem}[section]
\\newtheorem{lemma}[theorem]{Lemma}
\\begin{document}
\\maketitle
\\tableofcontents
\\newpage
${body}
\\end{document}
`;
}
// Compile with xelatex for Unicode/font support, 2 passes for TOC
// compileLatex(source, { engine: "xelatex", passes: 2 });
Express endpoint for on-demand PDF generation
import express from "express";
const app = express();
app.use(express.json());
app.post("/api/generate-report", async (req, res) => {
try {
const texSource = generateMathReport(req.body.title, req.body.sections);
const pdf = await compileLatex(texSource, {
engine: "xelatex",
passes: 2,
});
res.setHeader("Content-Type", "application/pdf");
res.setHeader("Content-Disposition", `attachment; filename="report.pdf"`);
res.send(pdf);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
Best Practices
- Always escape user-supplied text with a dedicated function before interpolating it into LaTeX source to prevent compilation errors and injection of arbitrary LaTeX commands.
- Use
xelatexorlualatexwhen you need Unicode characters or custom OpenType fonts; reservepdflatexfor simpler ASCII-only documents where speed matters. - Run at least two compilation passes when the document uses
\tableofcontents,\ref,\cite, or any cross-referencing — the first pass collects labels and the second resolves them.
Common Pitfalls
- Not escaping special characters (
&,%,$,#,_,{,},\,~,^) in dynamic content — a single unescaped ampersand will crash the entire compilation with a cryptic error. - Running LaTeX in a container or serverless function without a complete TeX distribution installed — missing packages cause silent failures. Use
texlive-fullor explicitly install every required package in the Docker image.
Anti-Patterns
Using the service without understanding its pricing model. Cloud services bill differently — per request, per GB, per seat. Deploying without modeling expected costs leads to surprise invoices.
Hardcoding configuration instead of using environment variables. API keys, endpoints, and feature flags change between environments. Hardcoded values break deployments and leak secrets.
Ignoring the service's rate limits and quotas. Every external API has throughput limits. Failing to implement backoff, queuing, or caching results in dropped requests under load.
Treating the service as always available. External services go down. Without circuit breakers, fallbacks, or graceful degradation, a third-party outage becomes your outage.
Coupling your architecture to a single provider's API. Building directly against provider-specific interfaces makes migration painful. Wrap external services in thin adapter layers.
Install this skill directly: skilldb add document-generation-services-skills
Related Skills
Docraptor
"DocRaptor: HTML-to-PDF API, Prince XML engine, CSS print styles, headers/footers, page breaks, async documents"
Docusaurus
Docusaurus: React-based static site generator for documentation sites, versioned docs, MDX support, search integration, i18n
Jspdf
jsPDF: client-side and server-side PDF generation in JavaScript, tables, images, custom fonts, autotable plugin
Markdoc
Markdoc: Stripe's Markdown-based authoring framework for structured documentation, custom tags, validation, and renderers
PDF Lib
"pdf-lib: create and modify PDFs in JavaScript, form filling, page manipulation, embedding images/fonts, digital signatures"
Puppeteer
"Puppeteer: headless Chrome, PDF generation from HTML, screenshots, web scraping, page automation, Chromium control"