Node.js / TypeScript
The Calco client is a zero-dependency connector built on the native fetch and
FormData APIs. It works in any Node.js 18+ runtime and ships full TypeScript
types.
Add the connector
There's no npm package to install (yet) — the client is a single, self-contained
TypeScript file. Copy index.ts from the Node connector into your project as
calco/index.ts (or add a path alias) so the calco imports below resolve:
import { CalcoClient } from "calco";Quick start
import { CalcoClient } from "calco";
import fs from "node:fs";
const client = new CalcoClient("http://localhost:8080", {
apiKey: "dc_live_...",
});
// Convert a local XLSX file to PDF
const pdf = await client.convert("./invoice.xlsx", { format: "pdf" });
await fs.promises.writeFile("invoice.pdf", pdf);The constructor takes the base URL and an optional { apiKey }. The key is sent
as Authorization: Bearer <key> on every request.
Converting a file
convert(filePath, options?) uploads a local file and returns the rendered
bytes as a Buffer. No template is stored.
const buf = await client.convert("./template.xlsx", {
format: "pdf", // "pdf" | "html" | "csv" | "ods" | "xls"
dataJson: JSON.stringify({ company: { name: "Acme" } }),
sheet: "Sheet1",
title: "Invoice #42",
});Extract the placeholder map from a template with scaffold:
const scaffold = await client.scaffold("./template.xlsx");
// { company: { name: "name0", address: "address0" }, items: ... }Managing templates
Upload a template once, then render it repeatedly by id and version.
// Create
const template = await client.createTemplate("./invoice.xlsx", {
name: "Invoice Template",
category: "billing",
tags: "invoice,billing,finance",
});
// List
const templates = await client.listTemplates();
// Update metadata
await client.updateTemplate(template.id, { name: "Updated Invoice" });
// Add a new version
const { version } = await client.addVersion(template.id, "./invoice-v2.xlsx");
// Delete (removes all versions)
await client.deleteTemplate(template.id);Rendering a stored template
const pdf = await client.renderTemplate(
template.id,
1, // version
{
company: { name: "Acme Corp" },
invoice: { number: "INV-001", total: "1,250.00" },
},
{ format: "pdf", title: "Invoice INV-001" },
);
await fs.promises.writeFile("output.pdf", pdf);Batch rendering
Render one document per record and receive a ZIP archive:
const zip = await client.batchRender(
template.id,
[
{ name: "Alice", amount: "100" },
{ name: "Bob", amount: "200" },
],
{ format: "pdf", titleTemplate: "report-{{index}}-of-{{n}}", sheet: "Sheet1" },
);
await fs.promises.writeFile("batch.zip", zip);By-hash workflow
Every stored version exposes a content hash (sha256 of the file). Address a
version directly by hash — no id or version number required:
const meta = await client.getTemplateByHash("3c9f...d1ab");
const pdf = await client.renderByHash("3c9f...d1ab", { customer: "Jane" }, { format: "pdf" });
const xlsx = await client.downloadByHash("3c9f...d1ab", { format: "xlsx" });Error handling
Non-2xx responses throw CalcoError, which carries the HTTP status and body:
import { CalcoError } from "calco";
try {
await client.convert("./bad-file.xlsx");
} catch (err) {
if (err instanceof CalcoError) {
console.error(`Status: ${err.status}`);
console.error(`Body: ${err.body}`);
}
}Framework integration
Express
import express from "express";
import { CalcoClient } from "calco";
const app = express();
const dc = new CalcoClient("http://localhost:8080");
app.get("/api/invoice/:id", async (req, res) => {
const data = await getInvoiceData(req.params.id); // your DB call
const pdf = await dc.renderTemplate("invoice-tpl-123", 1, data, {
format: "pdf",
title: `Invoice ${req.params.id}`,
});
res.setHeader("Content-Type", "application/pdf");
res.setHeader("Content-Disposition", `attachment; filename="invoice-${req.params.id}.pdf"`);
res.send(pdf);
});
app.listen(3000);NestJS
import { Injectable } from "@nestjs/common";
import { CalcoClient } from "calco";
@Injectable()
export class DocumentService {
private readonly client = new CalcoClient(
process.env.CALCO_URL ?? "http://localhost:8080",
);
generateInvoicePdf(data: Record<string, unknown>): Promise<Buffer> {
return this.client.renderTemplate("invoice-tpl-123", 1, data, { format: "pdf" });
}
}Next.js API route (App Router)
// app/api/invoice/[id]/route.ts
import { CalcoClient } from "calco";
const dc = new CalcoClient(process.env.CALCO_URL!);
export async function GET(_req: Request, { params }: { params: { id: string } }) {
const data = await getInvoiceData(params.id);
const pdf = await dc.renderTemplate("invoice-tpl-123", 1, data, {
format: "pdf",
title: `Invoice ${params.id}`,
});
return new Response(pdf, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": `attachment; filename="invoice-${params.id}.pdf"`,
},
});
}Exported types
import type {
Template,
TemplateVersion,
OutputFormat,
ConvertOptions,
CreateTemplateOptions,
UpdateTemplateFields,
RenderTemplateOptions,
BatchRenderOptions,
} from "calco";