Skip to content

Build reporting and analytics

Someone wants a dashboard, a monthly figure, or a spreadsheet they can send on. This guide is the reading order for the reporting side of an application, where the work is aggregation and presentation rather than transactions.

Numbers computed from data you hold — or from files you do not want to load — rendered as charts and tables, exported on demand or on a schedule. examples/inventory-app has the whole loop: a dashboard of stats, a bar chart, a low-stock table, a supplier table read straight from CSV, and a trend from accumulated snapshots.

1. Decide where the numbers live. If they are in your database, ordinary 2-way SQL is all you need. If they are in files — CSV or Parquet drops, exports from another system — duckdb.md makes them queryable in place, with no load step.

2. Read the whole loop once. analytics.md is the main page: files land, an ETL run summarizes them, history accumulates, and pages read the result. It is worth reading end to end before building any one part, because the parts are chosen together.

3. Build the dashboard. declarative-views.md covers view: dashboard — stat tiles, charts, and tables composed from a handful of SQL files with no HTML of your own.

4. Let people take it away. file-transfers.md covers CSV and Excel exports as routes. printable-documents.md adds PDF for the version that gets filed or signed.

5. Schedule the expensive part. A dashboard that aggregates three years of rows on every page load is a dashboard people stop opening. jobs.md covers running the summarization on a schedule and storing the result.

6. Send it out. A report nobody opens is a report nobody reads. notifications.md covers mailing the produced file when the run finishes.

  • Loading files that only need reading. A CSV drop queried through DuckDB needs no table, no migration, and no import job.
  • Aggregating live on every request. Summarize on a schedule, then read the summary.
  • Charting in the browser. The dashboard view renders charts server-side from SQL results; no front-end build is involved.
  • One giant query. Several named queries composed into one view read better and cache better than one query with six joins.