Quickstart
Run Xeplr Analytics locally, meaning the sign-in service, the API and the UI, against your own PostgreSQL and Redis.
This is the developer setup from the xeplr-bi repository. To install on a
server, use the deployment bundle instead: see Self-hosting.
Services, ports and databases
| Service | Port | Database (setting) |
|---|---|---|
| UI | 19100 | none |
Sign-in (xeplr-auth-server) |
19101 (AUTH_PORT) |
xeplr_bi_auth (AUTH_DB_NAME) |
| API | 19102 (BI_PORT) |
xeplr_bi (DB_API), xeplr_bi_jobs (DB_JOBS), xeplr_bi_workflow (DB_WORKFLOW) |
The API also uses the shared control-plane database xeplr_configs
(XCFG_DB_NAME) and an email templates database (EMAIL_DB_NAME).
You also need:
| What | Why |
|---|---|
| PostgreSQL | every database above |
Redis (default 127.0.0.1:6379) |
sessions, the access cache and single-use SSE tickets. The sign-in service refuses to start without it |
| Disk for DuckDB files | warehouse replicas and cube files, under DW_STORAGE_PATH |
| Sibling folders | the backend depends on ../automl/backend, ../warehouse/backend, ../../xeplr-workflow/backend, ../engine, ../query-engine and ../report-engine by file: path, so npm install fails if any are missing |
Settings
The API reads backend/development.env. No development.env.example is
committed. The annotated template is scripts/deploy/production.env.example.
*.env is git-ignored, so never commit values.
The API checks the required settings at startup and names every missing one at
once. npm run check-env runs the same check without starting anything.
| Setting | Meaning |
|---|---|
ENCRYPTION_KEY |
Decrypts the encrypted connection strings. If you change it, re-encrypt every connection |
BI_CONNECTION |
Encrypted database server login. Used for the api database, and passed as the connection for jobs and workflow |
AUTH_DB_CONNECTION_INFO_ENCRYPTED |
Login for the auth database: sign-in service, db:create, and the API’s cross-database bridge |
JOBS_CONNECTION |
Login npm run db:create uses for the jobs database |
XCFG_DB_CONNECTION_INFO_ENCRYPTED |
Login for xeplr_configs |
DB_API, DB_JOBS, DB_WORKFLOW, AUTH_DB_NAME, XCFG_DB_NAME, EMAIL_DB_NAME |
Database names. None has a fallback |
BI_PORT, AUTH_PORT |
19102 and 19101 in development |
AUTH_URL |
Where the API’s auth gate validates tokens (the sign-in service) |
AUTH_JWT_SECRET |
Signs access tokens |
AUTH_SUPER_ADMIN_EMAIL, AUTH_SUPER_ADMIN_PASSWORD |
The first account, created once when the auth database is first migrated |
XEPLR_AUTH_MIGRATIONS |
Comma-separated absolute paths: backend/migrations-auth, workflow’s and @xeplr/jobs’ migrations-auth |
REDIS_PREFIX |
bi:. Required by @xeplr/auth, and it must not be xeplr: |
EMAIL_PROVIDER |
smtp (with SMTP_*) or brevo (with BREVO_*) |
DW_STORAGE_PATH, UPLOAD_DIR, REPORT_ASSETS_DIR, LOG_DIR |
Storage. Defaults exist for development; use absolute paths anywhere else |
AUTH_SUPER_ADMIN_PASSWORD is substituted into SQL, so it must not contain
quotes, spaces, $ or #. The full list, including the optional REDIS_*,
IMAP_* and JOBS_* settings, is in backend/README.md.
Backend
cd backend
npm install
npm run db:encrypt # encrypt a DB login with ENCRYPTION_KEY, for the *_CONNECTION settings
npm run db:create # creates xeplr_bi, xeplr_bi_auth and xeplr_bi_jobs
npm run start-auth # sign-in on :19101; runs auth's migrations, then XEPLR_AUTH_MIGRATIONS
npm run start-api # API on :19102; runs migrations/, prepares xeplr_configs and jobs, mounts workflow
Set ENCRYPTION_KEY in development.env before db:encrypt. The template
suggests generating it with openssl rand -hex 32.
Check the API is up:
curl http://localhost:19102/
# { "service": "api", "status": "running", "name": "xeplr-bi-api" }
If the sign-in service is down, the API serves nothing, by design.
Backend scripts
| Script | What it does |
|---|---|
start-api |
NODE_ENV=development node ./bin/www: loads <NODE_ENV>.env, checks the required settings, runs migrations/, prepares xeplr_configs and jobs, mounts workflow, starts |
start-auth |
xeplr-auth-server with development.env |
migrate:up |
api migrations, then auth migrations, then jobs migrations |
migrate:status |
state of the api migrations |
db:create |
creates the api, auth and jobs databases |
db:encrypt |
xeplr-db-encrypt |
check-env |
the startup settings check, on its own |
test |
node test/run.mjs |
lint |
eslint . |
UI
cd ../ui
npm install
npm run dev # Vite on http://localhost:19100 (strictPort)
The UI has no environment variables. In development, Vite proxies /auth/api
to 19101 and the API’s prefixes (/datasets, /reports, /dashboards,
/warehouse, /cubes, /jobs, /workflow, /events and the rest) to 19102,
so the browser stays same-origin.
A path missing from that proxy table does not fail loudly. Vite answers an
unknown GET with index.html, so the page renders empty. Every new API prefix
needs an entry in vite.config.js, and test/devProxy.test.mjs checks this
for every authFetch path.
| Script | What it does |
|---|---|
dev |
Vite on 19100 |
build |
vite build into dist/ |
preview |
vite preview on 19100 |
test |
node test/run.mjs |
lint |
eslint . |
Sign in
Open http://localhost:19100 and sign in with AUTH_SUPER_ADMIN_EMAIL. Workspace
URLs start with the tenant, /:companyId/:workspaceId/…. With no company
chosen, the app sends you to /select-company first.
Roles
backend/migrations-auth/0001_bi_access.sql seeds these roles. The sign-in
service applies it through XEPLR_AUTH_MIGRATIONS.
| Role | Gets |
|---|---|
Super Admin |
every API, page and menu |
CompanyAdmin |
the reports:* and configuration:* groups |
Creator |
reports:view, reports:create, reports:edit |
Viewer |
reports:view |
Items (APIs, pages, menus, elements) are tagged with a group such as
reports:view, and roles are granted groups. A new route inherits access from
its group. Membership rows (userTenantsMapping) set which company or
workspace a role applies in. See Access and menus.
Tests
# from the xeplr-bi folder
(cd backend && npm test) # plain-node suites; needs the file: siblings and the DuckDB native module
(cd ui && npm test) # plain-node suites; no services
./test.sh # report-engine, ui, then backend
Next
- Self-hosting: the bundle, nginx and systemd
- Datasets and cubes: the model layer
- Security: what must be true before real data is connected
Last updated 16 September 2026