API
Generate invoices using your preferred framework.
Features
Preview invoices
Generate PDF previews of invoices using your configured templates.
Generate invoices
Produce PDF/A-3b compliant invoices from your templates using Puppeteer.
Authorization
Control access to API endpoints with customizable request-based authorization.
And more...
Trusted origins, RPC client, and other advanced options.
Installation
Install the package
Let's start by adding the API Plugin to your project.
npm install @node-zugferd/api
Setup API instance
The API instance is the entry point for handling requests to generate and preview invoices.
Start by importing api
and passing your zugferd (invoicer
) instance.
import { api } from "@node-zugferd/api";
export const zugferdApi = api({
invoicer,
// ...
});
At this stage, the call will proce a TypeScript error.
That's expected as api
still requires additional configuration before it can run:
renderer
: Tells the API how to render invoice templates (React, Vue, Svelte, etc.).templates
: One or more layouts that control the content of generated PDF invocies.
We'll configure these in the next steps.
Create a template
Templates define the layout and content of generated PDF invoices. The plugin comes with a renderer for all popular web frameworks, including vanilla html.
import { reactRenderer } from "@node-zugferd/api/react/renderer";
api({
invoicer,
renderer: reactRenderer(),
templates: {
myTemplate: (data) => {
return {
body: (
<h1>Invoice {data.number}</h1>
),
};
},
},
});
- Create a file called
template.vue
somewhere in your project
<template>
<h1>Invoice {{ number }}</h1>
</template>
<script setup lang="ts">
import type { invoicer } from "./your/path/invoicer";
defineProps<typeof invoicer.$Infer.Schema>>();
</script>
- Add the component to the config
import { vueRenderer } from "@node-zugferd/api/vue/renderer";
import Template from "./your/path/template.vue";
api({
invoicer,
renderer: vueRenderer(),
templates: {
myTemplate: (data) => {
return {
body: {
component: Template,
props: data,
},
};
},
},
});
- Create a file called
template.svelte
somewhere in your project
<script>
import type { invoicer } from "./your/path/invoicer";
export let data: typeof invoicer.$Infer.Schema;
</script>
<h1>Invoice {data.number}</h1>
- Add the component to the config
import { svelteRenderer } from "@node-zugferd/api/svelte-kit/renderer";
import Template from "./your/path/template.svelte";
api({
invoicer,
renderer: svelteRenderer(),
templates: {
myTemplate: (data) => {
return {
body: {
component: Template,
props: data,
},
};
},
},
});
import { solidRenderer } from "@node-zugferd/api/solid-start/renderer";
api({
invoicer,
renderer: solidRenderer(),
templates: {
myTemplate: (data) => {
return {
body: (
<h1>Invoice {data.number}</h1>
),
};
},
},
});
import { vanillaRenderer } from "@node-zugferd/api/vanilla/renderer";
api({
invoicer,
renderer: vanillaRenderer(),
templates: {
myTemplate: (data) => {
return {
body: `<h1>${data.number}</h1>`,
};
},
},
});
Mount Handler
To handle api requests, you need to set up a route handler on your server.
Create a new file or route in your framework's designated catch-all route handler. This route should handle requests for the path /api/zugferd/*
(unless you've configured a different base path).
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance
import { toNextJsHandler } from "@node-zugferd/api/next-js";
export const { POST, GET } = toNextJsHandler(zugferdApi);
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance
export default defineEventHandler((event) => {
return zugferdApi.handler(toWebRequest(event));
});
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance
import { svelteKitHandler } from "node-zugferd/svelte-kit";
export async function handle({ event, resolve }) {
return svelteKitHandler({ event, resolve, api: zugferdApi });
}
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance
import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node";
export async function loader({ request }: LoaderFunctionArgs) {
return zugferdApi.handler(request);
}
export async function action({ request }: ActionFunctionArgs) {
return zugferdApi.handler(request);
}
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance
import { toSolidStartHandler } from "@node-zugferd/api/solid-start";
export const { GET, POST } = toSolidStartHandler(zugferdApi);
import { Hono } from "hono";
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance
import { serve } from "@hono/node-server";
import { cors } from "hono/cors";
const app = new Hono();
app.on(["GET", "POST"], "/api/zugferd/\*\*", (c) => zugferdApi.handler(c.req.raw));
serve(app);
import express from "express";
import { toNodeHandler } from "@node-zugferd/api/node";
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance
const app = express();
const port = 8000;
app.all("/api/zugferd/*", toNodeHandler(zugferdApi)); // For ExpressJS v4
// app.all("/api/zugferd/*splat", toNodeHandler(zugferdApi)); For ExpressJS v5
// Mount express json middleware after node-zugferd handler
// or only apply it to routes that don't interact with node-zugferd
app.use(express.json());
app.listen(port, () => {
console.log(`node-zugferd app listening on port ${port}`);
});
This also works for any other node server framework like express, fastify, hapi, etc.
import { Elysia, type Context } from "elysia";
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance
const nodeZugferdView = (ctx: Context) => {
const NODE_ZUGFERD_ACCEPT_METHODS = ["GET", "POST"];
// validate request method
if (NODE_ZUGFERD_ACCEPT_METHODS.includes(ctx.request.method)) {
return zugferdApi.handler(ctx.request);
} else {
ctx.error(405);
}
}
const app = new Elysia().all("/api/zugferd/\*", nodeZugferdView).listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance
import { createAPIFileRoute } from "@tanstack/react-start/api";
export const APIRoute = createAPIFileRoute("/api/zugferd/$")({
GET: ({ request }) => {
return zugferdApi.handler(request);
},
POST: ({ request }) => {
return zugferdApi.handler(request);
},
});
Create Client Instance
The client-side library helps you interact with the zugferd api server.
import { createClient } from "@node-zugferd/api/client";
import type { zugferdApi } from "./your/path/invoicer"; // Import as type
export const zugferdClient = createClient<typeof zugferdApi>({
/** the base url of the server (optional if you're using the same domain) */
baseURL: "http://localhost:3000",
});
🎉 That's it!
That's it! You're now ready to use @node-zugferd/api
in your application.
Continue to basic usage to learn how to use the API Plugin to preview or generate invoices.
Basic Usage
The API Plugin provides endpoints for:
- Previewing PDF invoices
- Generating PDF/A-3b compliant invoices
Creating an invoice
To create an invoice we first need to define the data.
const data: typeof invoicer.$Infer.Schema = {
// ...
};
Then we need to call the endpoint with the data as body. This can be done client or server-side.
Server Side
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance
const result = await zugferdApi.api.create({
body: {
template: "myTemplate",
data, // pass the data defined previously
},
});
Client Side
import { zugferdClient } from "./lib/zugferd-client"; // path to your zugferd client
const { data, error } = await zugferdClient("@post/create", {
body: {
template: "myTemplate",
data, // pass the data defined previously
},
});
You can also use it with client-side data-fetching libraries like TanStack Query.
Previewing an invoice
To preview an invoice we first need to define the data.
const data: typeof invoicer.$Infer.Schema = {
// ...
};
Now we can finally call the endpoint.
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance
const result = await zugferdApi.api.preview({
body: {
template: "myTemplate",
data, // pass the data defined previously
},
});
Options
invoicer
The zugferd instance used to generate the invoice.
import { invoicer } from "./your/path/invoicer";
api({
invoicer,
});
renderer
Tells the API how to render invoice templates (React, Vue, Svelte, etc.).
See Create a template.
templates
One or more layouts that control the content of generated PDF invocies.
See Create a template.
authorize?
Restrict access to endpoints.
api({
authorize: async (ctx) => {
const user = await getUser(ctx.request);
if (user.role === "admin") {
return true;
}
return false;
},
});
baseURL?
Base URL for the node-zugferd api. This is typically the root URL where your application is hosted.
api({
baseURL: "https://example.com",
});
basePath?
Base path for the node-zugferd api. This is typically the path where the node-zugferd api routes are mounted
api({
basePath: "/api/zugferd",
});
Default: /api/zugferd
trustedOrigins?
List of trusted origins.
api({
trustedOrigins: ["https://example.com", "*.trusted.com"],
});
The baseURL is always included.
advanced?
Advanced configuration options.
api({
advanced: {
disableCSRFCheck: false,
puppeteer: {
lauch: {
headless: true,
args: ["--no-sandbox"],
},
},
},
});
disableCSRFCheck?
: Disable trusted origins check (âš security risk)puppeteer.launch?
: Launch options for puppeteer, SeeLaunchOptions
onAPIError?
API error handling configuration.
api({
onAPIError: {
throw: false,
onError: (error, ctx) => {
console.error("API Error:", error);
},
},
});
throw
: Throw an error on API error (default:true
)onError
: Custom error handler
disabledPaths?
Disable specific api paths.
api({
disabledPaths: ["/preview"],
});