NAME
Database::BI - Web-based Business Intelligence viewer for flat data files
VERSION
0.001.0
SYNOPSIS
Start the development server (restarts automatically when you edit a file):
morbo script/database-bi
Start the production server:
hypnotoad script/database-bi
Use a different data directory:
# In database_bi.conf (create this file in the same folder as script/):
{ data_dir => '/home/user/data' }
Change the language used for templates:
# In database_bi.conf:
{ data_dir => 'data', language => 'fr', platform => 'web' }
# Then create templates/web/fr/ and put your French .html.tt files there.
Run the test suite to verify everything is working:
make test
Generate the Makefile for the first time or after editing Makefile.PL:
perl Makefile.PL
DESCRIPTION
Database::BI is a Mojolicious web application that reads arbitrary
flat data files (CSV, PSV, SQLite, XML, etc.) via Database::Abstraction
and presents them as styled, sortable, reorderable HTML tables.
Key features:
- File picker - the home page scans
data_dirand shows a card for every supported file. Recently opened filesystem files appear in a "Recently opened" section powered bylocalStorage. - Filesystem browser -
/browselets the user navigate the entire filesystem and open any supported data file, not just files indata_dir. - Column sort and reorder - clicking a header sorts the table; headers
are draggable to reorder. Both settings are persisted in
localStorageby column name and survive page reloads. - Left join - the "Merge data / Filter results" panel on any table view lets the user join one or more additional tables on a shared key. Every left row is kept; right-table columns are appended for matching rows.
- Result filters - the same panel lets the user add filter conditions
(column / operator / value) that are applied server-side after all joins.
Operators:
eq,ne,contains,starts,lt,le,gt,ge,empty,notempty. Active filters are shown as chips in the toolbar with a one-click "Clear" link. - Drag-and-drop upload - any supported data file can be dropped directly onto the application. On the home page the file is opened immediately; when the join panel is open the dropped file populates the right-table path field.
- Export - the toolbar on any view offers an export panel that writes
the current logical view (after joins and filters) to a chosen filesystem
path as CSV (
.csv) or SQLite (.sql).
ROUTES
-
GET /Scans
data_dir, renders a card grid of available tables. -
GET /view/:tableOpens the named table from
data_dir. Accepts?f=col:op:val(repeatable) to pre-filter results. -
GET /browseFilesystem navigator. Accepts
?path=to set the starting directory (defaults to$HOME). -
GET /openOpens any supported file by absolute path (
?path=). Accepts?f=filters. -
GET /joinPerforms one or more left joins and renders the merged table. Parameters:
l=<spec> left table: "table:name" or "path:/abs/path" j=<spec>|<lk>|<rk> join step (repeatable): right-spec, left key, right key f=<col>:<op>:<val> result filter (repeatable) -
GET /api/columnsReturns
{ "columns": [...] }for a table (?table=name) or file (?path=/abs/path). Used by the join UI to populate the right-key dropdown without a page reload. -
GET /exportExports the current logical view (same
l=,j=,f=parameters as/join) as a file download. Additional parameter:format=csv (default) - RFC 4180 CSV; UTF-8; CRLF line endings format=sqlite - SQLite 3 database with a single table named "data"The download filename is derived from the left table label with non-alphanumeric characters replaced by underscores.
-
POST /exportWrites the current logical view to a chosen filesystem path. Body params:
l=,j=,f=(same as GET), plusdir=(target directory) andfilename=(name including extension; extension determines format:.csvor.sql). Returns JSON{ saved: "/abs/path" }or{ error: "..." }. -
GET /api/dirsReturns a JSON directory listing (subdirectories only) for the export panel's inline directory browser. Accepts
?path=(defaults to$HOME). Returns{ path, parent, dirs: [{name, path}] }. -
GET /api/statReturns filesystem metadata for a file path (
?path=). Returns{ exists, path, mtime, size }. If the file does not exist,existsisfalseand the remaining fields are absent (HTTP 200). Returns HTTP 400 whenpathis missing. -
POST /uploadAccepts a multipart file upload (field name:
file), validates the extension, saves to a managed.uploads/subdirectory under the app home, and returns JSON{ url, path }. -
GET /importFetches an HTML table from a public URL (
?url=) and renders it as a data grid. An optional?t=parameter (zero-based integer) selects which HTML table on the page to display when the page contains multiple tables.
CONFIGURATION
Place a database_bi.conf file in the application root to override
defaults:
{
data_dir => 'data', # directory scanned for data files on the home page
platform => 'web', # VWF template dimension
language => 'en', # VWF template dimension
}
COMMON PITFALLS
-
The configuration file is optional but must be valid Perl if present
database_bi.confis loaded byMojolicious::Plugin::Config, which evaluates it as a Perl data structure. If the file exists but contains a syntax error, the application will refuse to start. If the file does not exist, built-in defaults are used and no error occurs. The file must return a hashref:# database_bi.conf -- correct { data_dir => 'data', platform => 'web', language => 'en' } # WRONG -- missing braces data_dir => 'data' -
data_dir is relative to the application home directory, not the process cwd
Setting
data_dir => 'data'looks for a folder calleddata/in the same directory as thescript/database-bilauncher, regardless of where you run the server from. An absolute path works on any system:{ data_dir => '/var/db/mydata' } -
The download_dir default is computed once at startup
When the application starts, it picks the export directory in this order:
~/Downloads(if it exists), then$HOME, then the system temp directory. This value is fixed for the life of the process. Renaming or creating~/Downloadsafter the server starts has no effect. To force a different default, set it before starting:{ data_dir => 'data' } # and create ~/Downloads before starting the server -
Adding a new language requires a template directory, not just a config change
Setting
language => 'de'indatabase_bi.conftells the controller to look for templates intemplates/web/de/. If that directory does not exist, the controller automatically falls back to the default language. To add German support: (1) createtemplates/web/de/, (2) copy and translate the.html.ttfiles fromtemplates/web/en/, then (3) set the config. -
Supported data file extensions are: csv, db, sql, xml, psv
The application calls
Database::Abstractionwhich recognises exactly these five extensions. A file calledinventory.sqliteis not recognised -- it must be renamed toinventory.sql. A file calleddata.xlsx(Excel) is also not supported; export it as CSV first. -
The open_table helper lowercases the table name
When the router matches
GET /view/SalesorGET /view/SALES, the table name is lowercased tosalesbefore being passed to the helper. The data file on disk must therefore also be lowercase (sales.csv, notSales.csv).
LIMITATIONS
- Only read operations on data files are supported. Write-back (editing cell values in the browser and saving them to the data file) is not implemented.
- The left-join engine (
Dashboard::_left_join) is an in-memory O(n*m) hash join. It is suitable for BI files that fit comfortably in RAM. For very large files, replace theopen_tablehelper body with aDatabase::Joininstance (Phase 2) without changing the controller. - The
.uploads/directory grows indefinitely; no automatic eviction is performed. Users may delete.uploads/at any time to reclaim space. Sub::Private/:Private enforcement relies on the CHECK compilation phase. When a module is loaded dynamically at test time (e.g. viaTest::Mojo-new(...)>), the CHECK phase has already passed and the "Too late to run CHECK block" warning is emitted -- the private restriction is not enforced in that context. This is a known limitation ofSub::Privateand does not affect production (morbo/hypnotoad) deployments where the module is compiled on startup.
SEE ALSO
REPOSITORY
https://github.com/nigelhorne/Database-BI
SUPPORT
This module is provided as-is without any warranty.
AUTHOR
Nigel Horne <njh@nigelhorne.com>
LICENCE AND COPYRIGHT
Copyright 2026 Nigel Horne.
Usage is subject to the GPL2 licence terms. If you use it, please let me know.