Fully featured report generation in Node with the JSReports rendering core
Tags: node, jsreports, reports, templates
Published: 2023-05-26 05:39:03

(Check out [this repo](https://github.com/moofoo/nestjs-jsreport-examples) to get an idea of what's possible)

If you're interested in a free, self-hosted NodeJS report generation solution that doesn't compromise on templating functionality, you should check out [JSReport](https://jsreport.net/), specifically the functionality one can build using the jsreport '[rendering core](https://github.com/jsreport/jsreport/tree/master/packages/jsreport-core)'. 

JSReport is open-source, and unlike many SaaS providers in this space, they do not gatekeep **ANY** of their product's core functionality behind tiered subscription pricing. While JSReport does charge for their web-based 'Template Studio' and SaaS offerings, the report generation framework that powers these products is open source and free to use however you want.

Let's get started with a basic example.

Open up your terminal (I'm assuming linux, no promises if you're in Windows-land) and run the following:

```console
git clone https://github.com/moofoo/basic-jsreport-core-example.git && \
cd basic-jsreport-core-example && \
yarn

```

First, lets look at the [package.json]()

```json
{
  "name": "basic-jsreport-core-example",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "@jsreport/jsreport-core": "^3.11.4",
    "@jsreport/jsreport-docx": "^3.7.1",
    "@jsreport/jsreport-handlebars": "^3.2.1",
    "@jsreport/jsreport-unoconv": "^3.0.1",
    "handlebars": "^4.7.7"
  }
}

```

[`@jsreport/jsreport-core`](https://github.com/jsreport/jsreport/tree/master/packages/jsreport-core) is the "minimalist jsreport rendering core", on to which we'll add 'extensions' (plugins, what have you), to build up the functionality we want.

JSReport categorizes extensions into three categories: Engines, Recipes, Everything Else

1. Engines - These are templating languages, i.e. handlebars, ejs, etc. The current example uses handlebars, by way of the `@jsreport/jsreport-handlebars` extension. For any given Engine you will also need the actual templating language in your dependencies - `handlebars@4.7.7` in this case.

2. Recipes 
> "A recipe represents the technique used to print the document. It can be an HTML to pdf conversion, DOCX rendering, and others."

The current example employs the `docx` recipe, so we have the `@jsreport/jsreport-docx` extension as a dependency.

3, Everything Else - for instance, this repo uses the `@jsreport/jsreport-unoconv` extension to do PDF conversion via unoconv.

Now let's look at [index.js](https://github.com/moofoo/basic-jsreport-core-example/blob/main/index.js). Here's that file:

```typescript
const { readFileSync, writeFileSync, unlinkSync } = require("fs");
const templateData = require('./data.json');

// remove existing report files, if they exist
try {
  unlinkSync("./report.docx");
  unlinkSync("./report.pdf");
} catch (err) {
  console.log("no report files");
}

// Create jsreport instance
const jsreport = require("@jsreport/jsreport-core")();

// Add plugins: docx recipe, handlebars engine and unoconv extension for pdf conversion
jsreport.use(require("@jsreport/jsreport-docx")());
jsreport.use(require("@jsreport/jsreport-handlebars")());
jsreport.use(require("@jsreport/jsreport-unoconv")());

(async () => {
  // Initialize jsreport instance
  await jsreport.init();

  // Read template.docx, base64 encoded
  const content = readFileSync("./template.docx", {encoding: "base64"});

  // Render a .docx report from template
  const resultDocx = await jsreport.render({
    template: {
      content: "",
      recipe: "docx",
      engine: "handlebars",
      docx: {
        templateAsset: {
          content,
          encoding: "base64",
        },
      },
    },
    data:templateData
  });

  // Write report.docx to disk
  await writeFileSync("./report.docx", resultDocx.content, {encoding: "utf-8"});
  console.log("report.docx created");

  // Render a .pdf report from template using unoconv extension
  const resultPdf = await jsreport.render({
    template: {
      content: "",
      recipe: "docx",
      engine: "handlebars",
      docx: {
        templateAsset: {
          content,
          encoding: "base64",
        },
      },
      unoconv: {
        format: "pdf",
        enabled: true,
      },
    },
    data: templateData
  });

  // Write report.pdf to disk
  await writeFileSync("./report.pdf", resultPdf.content, "binary");
  console.log("report.pdf created");
})();

```


Let's go over the JsReport-relevant lines. We start with some initialization boilerplate:


```typescript
// Create jsreport instance
const jsreport = require("@jsreport/jsreport-core")();

// Add plugins: docx recipe, handlebars engine and unoconv extension for pdf conversion
jsreport.use(require("@jsreport/jsreport-docx")());
jsreport.use(require("@jsreport/jsreport-handlebars")());
jsreport.use(require("@jsreport/jsreport-unoconv")());

```

The next lines initialize the instance given the added extensions and gets the template file:

```typescript
  // Initialize jsreport instance
  await jsreport.init();

  // Read template.docx, base64 encoded
  const content = readFileSync("./template.docx", {encoding: "base64"});

```
Note the base64 encoding.


At this point, the jsReport instance is ready to render docx (or pdf) reports from .docx templates:
<a href="#" id="jsreport-render" />
```typescript
// Render a .docx report from template
  const resultDocx = await jsreport.render({
    template: {
      content: "",
      recipe: "docx",
      engine: "handlebars",
      docx: {
        templateAsset: {
          content,
          encoding: "base64",
        },
      },
    },
    data:templateData
  });

  // Write report.docx to disk
  await writeFileSync("./report.docx", resultDocx.content, {encoding: "utf-8"});
  console.log("report.docx created");
```

Let's go over the options passed to jsreport.render, specifically the template config:

`content: "",`
- While the content param isn't used here,  JsReport will throw an error if it is undefined. An empty string suffices.

`recipe: "docx"`
- This sets the render 'recipe'. Regarding naming conventions:

> "The convention is that jsreport repository extension starts with jsreport-xxx, but the extension real name and also the recipes or engines it registers excludes the jsreport- prefix. This means if you install extension @jsreport/jsreport-handlebars the engine's name you specify in the render should be handlebars."

Accordingly, `@jsreport/jsreport-docx` transmutes to 'docx', here.

`engine: "handlebars"`
- same story


```typescript
docx: {
  templateAsset: {
    content,
    encoding: "base64",
  },
},

```

`content` is just the template.docx file, base64 encoded, and `encoding` informs the render method about that encoding.

The next [jsreport.render call](https://github.com/moofoo/basic-jsreport-core-example/blob/8f15fd3c8f43f6f1d2ff118b91e2a294bd8a9f6e/index.js#L49) is identical to the previous one, with one addition to the `template` options:

```
 unoconv: {
    format: "pdf",
    enabled: true,
 },

```

So, when this render runs, a PDF will be created, rather than a .docx file. Note that enabled is assumed to be false if undefined, so it must be set to `true` here.

Using whatever document viewer you have handy, check out whats in [template.docx](https://github.com/moofoo/basic-jsreport-core-example/raw/main/template.docx). This is just an invoice example I pulled from the [JSReport Playground](https://playground.jsreport.net/) 

Ok, let's run this thing:

```
node index.js
``` 

Assuming nothing goes horribly wrong, you should now have two report files in the repo root, `report.pdf` and `report.docx`.

### --------------------

While the code presented here is nothing fancy, the real magic is how this extremely simple setup gives you full access to JSReport's robust templating features, for example:

- [embedded html](https://jsreport.net/learn/docx#docxhtml)
- [images](https://jsreport.net/learn/docx#docximage)
- [lists](https://jsreport.net/learn/docx#docxlist)
- [tables](https://jsreport.net/learn/docx#docxtable)
- [charts](https://jsreport.net/learn/docx#docxchart)
- [form inputs](https://jsreport.net/learn/docx#forms)
- [watermarks](https://jsreport.net/learn/docx#docxwatermark)
- [raw XML](https://jsreport.net/learn/docx#docxraw)
- [conditional page breaks](https://jsreport.net/learn/docx#docxpagebreak)

And that's just for [.docx based templates] (https://jsreport.net/learn/docx).


That's all for now!

NestJS + JSReport integration will be covered in a future article, however the repo for that article is already complete: https://github.com/moofoo/nestjs-jsreport-examples