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/jobsmigrations-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

Last updated 16 September 2026