FreeReportv0.9.0한국어

Backend examples

Serve report definitions and data as JSON from Node.js, Java Spring Boot, C# ASP.NET Core, Python, PHP or Go, and record the output log; any backend works.

FreeReport lays out, prints and saves files in the browser, so the server only answers with JSON: the report form and the rows. Any language and framework works, and nothing has to be installed on the server. The examples below implement the same four endpoints, so the browser code stays the same whichever backend you pick.

EndpointReturnsDescription
GET /api/reports/{name}JSONThe report form (definition) saved from the designer.
PUT /api/reports/{name}204Save the form (designer onSave). Check the name and that the body is JSON.
GET /api/reports/sales/data?from=&to={ rows: [...] }Rows for the parameters (viewer fetchData). Keys are the field names used in the form.
POST /api/report-logs204One print or download (viewer onOutput): kind, report, pages, rows, params.

Browser (the same for every backend)

import { createViewer } from '@freereportjs/core';
import '@freereportjs/core/styles.css';

const json = (url: string, init?: RequestInit) =>
  fetch(url, init).then(r => { if (!r.ok) throw new Error(r.status + ' ' + url); return r.json(); });

// 1. the report form (made in the designer)  2. rows for the parameters  3. output log
const viewer = createViewer(document.getElementById('viewer')!, {
  definition: await json('/api/reports/sales'),
  params: { from: '2026-10-01', to: '2026-10-31' },
  fetchData: params => json('/api/reports/sales/data?' + new URLSearchParams(params as Record<string, string>)),
  onOutput: e => fetch('/api/report-logs', {
    method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(e),
  }).then(r => r.ok),                     // false: the server refused, nothing is printed or saved
  lang: 'ko',
});

// designer: createDesigner(el, { onSave: def => fetch('/api/reports/sales', { method: 'PUT', body: JSON.stringify(def), headers: { 'content-type': 'application/json' } }) })

Server examples

// Node.js 20+ · npm i express@5 pg @freereportjs/core
import express from 'express';
import { readFile, writeFile } from 'node:fs/promises';
import pg from 'pg';
import { loadFonts, renderPdf } from '@freereportjs/core';

const app = express();
const db = new pg.Pool();                                  // PGHOST, PGUSER, … from the environment
app.use(express.json({ limit: '5mb' }));

const file = name => {
  if (!/^[\w-]+$/.test(name)) throw Object.assign(new Error('bad name'), { status: 400 });
  return 'reports/' + name + '.json';
};
const salesRows = async (from, to) => (await db.query(
  `SELECT to_char(sold_on, 'YYYY-MM-DD') AS date, region, product, qty, amount::float8 AS amount
     FROM sales WHERE sold_on BETWEEN $1 AND $2 ORDER BY region, sold_on`, [from, to])).rows;

// report form: the viewer loads it, the designer saves it
app.get('/api/reports/:name', async (req, res) => { res.type('json').send(await readFile(file(req.params.name), 'utf8')); });
app.put('/api/reports/:name', async (req, res) => {
  await writeFile(file(req.params.name), JSON.stringify(req.body, null, 2));
  res.sendStatus(204);
});

// data for the viewer's parameters
app.get('/api/reports/sales/data', async (req, res) => {
  res.json({ rows: await salesRows(String(req.query.from), String(req.query.to)) });
});

// output log (viewer onOutput)
app.post('/api/report-logs', async (req, res) => {
  const { kind, report, pages, rows, params } = req.body;
  await db.query('INSERT INTO report_log (user_id, kind, report, pages, row_count, params, ip) VALUES ($1, $2, $3, $4, $5, $6, $7)',
    [req.user?.id ?? null /* from your auth middleware */, kind, report, pages, rows, JSON.stringify(params), req.ip]);
  res.sendStatus(204);
});

// only JavaScript runtimes can also make the PDF on the server (batch jobs, e-mail attachments)
const fonts = await loadFonts(['Nanum Gothic']);
app.get('/api/reports/sales/pdf', async (req, res) => {
  const definition = JSON.parse(await readFile(file('sales'), 'utf8'));
  const rows = await salesRows(String(req.query.from), String(req.query.to));
  res.type('pdf').send(Buffer.from(await renderPdf(definition, { rows }, { fonts, params: req.query })));
});

app.listen(3000);

Points to check

  • Return dates as "yyyy-MM-dd" strings and amounts as numbers, so formats and sums work. The keys of each row are the field names in the form.
  • Check the report name before using it in a file path, and check that a saved form is JSON. Put the save and log endpoints behind your login.
  • Keep forms in a database table instead of files if you run several servers: the definition is one JSON text column.
  • PDF or Excel files made on the server need a JavaScript runtime (renderPdf and renderXlsx in Node.js, Deno or Bun). From Java, C#, Python, PHP or Go, call a small Node service, or let the browser make the file. Server rendering
  • Spring with JSP (eGovFrame): see the downloadable sample project. Frameworks and eGovFrame