Ritzy One • Technical Documentation

Technical Specifications & System Requirements

What a technical evaluator, IT manager or hosting partner needs before deploying Ritzy One — the technology stack, the architecture, the security model, server and client requirements, ports, deployment options and the backup strategy.

Applies to the current Ritzy One web platform  |  Written for customer IT departments and integrators

1 Platform overview

Ritzy One is a browser-based ERP suite, a re-platform of the long-running Ritzy desktop application. It is a single Node.js API process talking to a PostgreSQL database, with a React single-page application for the desk and a family of installable phone apps (PWAs) for the field.

AttributeValue
Application typeSingle-page application over a REST API — no application server licence, no Java or .NET runtime
Deployment modelOn-premise, private VM/VPS, or Docker — one instance per customer
TenancySingle-tenant per install; one PostgreSQL database per install holds every company, each company's rows isolated by Row-Level Security
LanguageTypeScript end to end, strict mode (including noUncheckedIndexedAccess)
Codebasenpm-workspaces monorepo — 16 packages, roughly 140,000 lines of first-party TypeScript
Licensed modulesThe Accounts core plus ten independently licensed modules: Cargo, Liner, Marine, Warehousing, Transport, Shipchandling, Inventory, Payroll, CRM, Real Estate
No native dependencies
The whole server side is pure JavaScript — including the PostgreSQL client (pg, without pg-native). There is no libpq to match, no compiler toolchain needed at install time, and no compiled add-on to rebuild when Node or the platform changes. That is what lets the same build run unchanged on Windows, on Linux and inside a container.

2 Technology stack

LayerTechnologyVersionNotes
RuntimeNode.js18.x or 20.x LTS (20 recommended)The Windows installer fetches Node 20 LTS if it is absent
API frameworkExpress4.21REST over JSON; no GraphQL
Language / buildTypeScript5.7tsc for the API; tsc -b plus Vite for the front ends
DatabasePostgreSQL18 (17 minimum)Installed by the Windows installer if missing, or bundled inside the Docker stack
Database driverpg (node-postgres)8.23Pure-JavaScript PostgreSQL client with a connection pool; no native bindings
Front endReact + Ant DesignReact 18.3, antd 5.22React Router 7, Axios, Day.js, Recharts
BundlerVite6.0Static output served by nginx (or by the API itself in the container build)
AuthenticationJSON Web Tokens + TOTPjsonwebtoken 9Passwords hashed with bcryptjs
Documentsdocxtemplater + Handlebars3.69 / 4.7Real Word .docx templates; PDF through LibreOffice where installed
Mail / pushnodemailer, web-push9.0 / 3.6SMTP held per company in the database; VAPID keys for push
AIAnthropic Claude SDK0.106Entirely optional — see §14
Process managerPM2 (Windows service via NSSM) / systemd / Docker—Survives reboot on every platform
Web servernginxcurrent stableServes the static builds and proxies /api/ to the one API process

Deliberate exclusions

3 Architecture

Browser / phone→ nginx — TLS, static, /api proxy→ Node API (Express)→ PostgreSQL 18→ one database — every company

There is exactly one API process. Every front end — the desk application and all twelve phone apps — is a separate static bundle that nginx publishes on the one address — the desk application at /, each phone app under its own path (/track/, /crm/ …) — proxying /api/ back to that single process. One host name and one certificate cover them all. Adding a phone app adds a static site, not a service.

Packages

PackageRole
packages/apiThe Express REST API — 67 route modules, the posting engines, reporting, document generation, licensing and the scheduled jobs
packages/webThe full ERP desk application (React + Ant Design)
packages/*-pwa × 12Track, Dues, CRM, Delivery, Notify, Ask, Support, Tenant, Jobs, Inspect, Letting, Collect
packages/supportShared client library behind the support assistant in every front end
packages/license-serverThe vendor-side licence issuing and tracking service — it runs at Ritzy, not at the customer

State and scaling

The API keeps no session state in memory — authentication is a signed token and session records live in the database — so it can be restarted without logging anyone out. Scaling is nonetheless vertical by design: the bottleneck in this workload is database I/O rather than Node CPU, and one PostgreSQL server per customer is ample. The multi-customer answer is one instance per customer, not a shared cluster.

4 Data layer & database design

One database per install

Every company in an install lives in one PostgreSQL database. The companies table is the company registry and drives the login page's company dropdown. Every business table starts with company_id, and Row-Level Security limits each connection to the company it was opened for; consolidated group reports switch it to read every company. Attachments and scanned documents are held in the same database rather than in a separate file.

ItemDetail
DatabasePostgreSQL 18, UTF-8, created with the built-in C.UTF-8 locale so codes and names sort the same way on every operating system
SchemaAbout 400 tables (394 in the baseline, the rest added by later migrations), 357 baseline foreign keys, 35 views
KeysEach table has a surrogate key; business numbers (voucher, account code, job number) are unique columns; document numbering uses sequences plus a locked series table
TypesMoney is exact NUMERIC (never floating point), flags are boolean, dates are date or timestamptz, text is text, binary is bytea
LoginsPG_URL (superuser) prepares and upgrades the database on start; PG_APP_URL (ritzy_app) serves every request and is subject to Row-Level Security
<RITZY_ROOT>Install-wide state files under Ritzy\System, and the default backup target Backups\PostgreSQL

Schema and upgrades

On start the application creates its own database, schema, application login and upgrades from PG_URL, so there is no separate database-setup step. The schema is a baseline; later changes are numbered SQL migrations applied in order, each in its own transaction and recorded with a checksum in schema_migrations. The same runner upgrades an existing install when a new build is deployed, so the database and the application version stay in lockstep. A new company is seeded with its chart of accounts and defaults in one transaction, and adding a module is a data change, not a schema change.

Business logic in TypeScript

The desktop application kept much of its logic in Firebird stored procedures and triggers. In Ritzy One that logic — voucher posting, ledger and party balances, bill allocation, inventory costing and stock posting — runs in TypeScript services, inside the same database transaction as the document it posts, and is covered by regression tests against the desktop application's results. Only a few read-only queries remain as SQL functions.

Engineering notes

Why PostgreSQL

PostgreSQL is open-source with no per-seat or per-core licence cost, runs identically on Windows, Linux and in containers, and is supported by every cloud provider as a managed service. Exact numeric types, real foreign keys and Row-Level Security give the data stronger guarantees than the desktop schema had, and standard tools — pg_dump, pg_restore, streaming replication — cover backup and standby.

Moving from the Ritzy desktop application

Desktop customers' data is in Firebird .FDB files. A one-off import converts each company into the PostgreSQL database: from the administration console (Import from Firebird, as a new company or replacing an existing one) or with npm run pg:convert. Problem rows are logged rather than silently dropped. This import is the only part of Ritzy One that reads Firebird; the running server does not need Firebird installed.

5 API design

Module gating

A gated module must be both installed in the company database and entitled by the subscription licence before its routes answer, and the check is on the server rather than only in the menu. It deliberately fails open if the entitlement lookup itself errors, so an infrastructure problem can never lock a paying customer out of their own data.

6 Security model

ControlImplementation
AuthenticationUsername and password against the company database, issuing a signed JWT. Access token lasts 8 hours by default, the refresh token 7 days; both configurable per install.
Token signingHMAC with a per-install secret generated randomly by the installer — never a shipped default.
PasswordsHashed with bcrypt. Existing desktop accounts keep working through the original cipher, so no password reset is forced at cut-over.
Two-factorTOTP (RFC 6238, any authenticator app), enrolled per user, and mandatory on the internal support account.
AuthorisationThe desktop application's own rights model is preserved: per-group, per-form view / add / edit / delete rights, enforced server-side on every call and used to build the menu.
Database accessPostgreSQL listens locally and logs in with SCRAM-SHA-256. Requests run as a dedicated application login (ritzy_app) that does not own the tables, so Row-Level Security confines every query to the signed-in company; the superuser login is used only to prepare and upgrade the database on start. Where the database is on another host, the connection uses TLS (sslmode in the connection URL).
Browser transportTLS terminates at nginx; a ready configuration ships in the bundle. Let's Encrypt/certbot on Linux, IIS ARR or a certificate bound in nginx on Windows.
CORSRestricted to the configured origins.
Session controlSessions are recorded per device in the database and can be ended centrally. A device that has been disabled, or whose app has been withdrawn, is rejected at the next token refresh rather than only at the next login.
Privileged toolingThe support console that can run SQL and replace database files sits behind three independent gates: a dedicated internal account, mandatory two-factor on it, and a rotating support password re-checked on the server for every change it makes.
Data residency
Nothing leaves the customer's server by default. The optional AI features send only the text of the specific question or document being processed; the optional licence heartbeat sends an install identifier and seat counts. Both are described in §13–14, and both can be left switched off. The one exception is company registration (§7): when a company is created, or its Company settings are saved, the install tells the Ritzy License Server the company's code and name and its setup details — contact email and telephone, city, country, base currency, modules and the server's host name. No business data is sent.

7 Licensing & entitlement

Licensing is a signed-token scheme. A licence is issued by the vendor-side Ritzy License Server, signed with a private key, and verified locally against the matching public key — so entitlement checks keep working with no internet connection.

8 Server requirements

8.1 Supported platforms

PlatformStatusHow it is installed
Windows Server 2016+, or Windows 10/11 ProPrimary, in productioninstall.ps1 — PM2 plus NSSM services, nginx, firewall rules
Linux (Ubuntu LTS, Debian, AlmaLinux)Supportedinstall-linux.sh — systemd and nginx
Docker / Docker Compose (Linux or WSL2)Supported, self-containeddocker compose up -d; PostgreSQL 18 is inside the bundle, so there is nothing to install

8.2 Hardware

ProfilevCPURAMStorageSuits
Minimum / evaluation24 GB40 GB SSDPilot, one company, a handful of users
Recommended production48 GB100 GB NVMeOne company, 10–30 concurrent users, normal reporting
Report-heavy or multi-company4–816 GB250 GB NVMeSeveral companies, long histories, heavy trial-balance and ageing runs

Component footprint on a lean install: idle Linux around 0.5–1 GB, the Node API 0.3–0.5 GB, and PostgreSQL with a useful cache 1–2 GB. A 3 GB tenant VM is comfortable and 4 GB generous — which is why a 32 GB host runs roughly six to eight fully isolated tenant VMs with headroom.

Storage is the specification that matters
This is a database workload. Insist on NVMe or provisioned-IOPS storage, and benchmark before trusting production data to it — a random read/write test plus a real pg_restore and a report run. Oversubscribed budget-VPS disks are the commonest cause of disappointing performance.

8.3 Software prerequisites

RequirementVersionNotes
PostgreSQL18 (17 minimum)Installed by the Windows installer if missing (EDB installer, silent; service postgresql-x64-18); an older local PostgreSQL stops the installer until it is moved to 18. Logins use SCRAM-SHA-256; data checksums on. One database holds every company. The app creates the database, schema, upgrades and its own login on start, from PG_URL. Inside the Docker bundle already.
Firebird—Not needed by the server. Only the one-off import of a Ritzy desktop customer's data reads their .FDB files (the administration console's Import from Firebird, or npm run pg:convert), on a machine that has Firebird.
Node.js20.x LTS or laterInstalled automatically by the Windows installer if missing.
nginxcurrent stableDownloaded by the Windows installer if absent.
LibreOfficeany current releaseOptional. Only needed to render Word quotation and purchase-order templates to PDF.

9 Client requirements

ClientRequirement
Desk browserAny current evergreen browser — Chrome, Edge, Firefox or Safari. No plug-in, no Java, no ActiveX, nothing installed on the workstation.
Screen1366×768 or better for the desk application; the phone apps are built for phone widths.
PhonesAndroid with Chrome, or iPhone with Safari. The apps install to the home screen straight from the browser — no app store, no MDM package.
Phone features usedCamera (QR scanning, photos, visiting-card capture), microphone (spoken questions), location and signature capture — each behind the browser's own permission prompt. Several field apps hold work offline and sync when a signal returns.
NetworkLAN, VPN or public HTTPS. Bandwidth needs are modest: JSON payloads against a cached static bundle, with no thick-client chatter and no database traffic to the workstation.
Push notificationsStandard Web Push (VAPID). Requires HTTPS, and on iPhone that the app has been added to the home screen.

10 Ports & network

The installer opens these inbound TCP rules. For an internet-facing deployment, publish 443 only and put nginx in front of everything.

PortService
80 / 443Desk web application and every phone app under its path (nginx)
3001The API process — proxied by nginx, does not need to be public
5432PostgreSQL — local to the server; never expose it

Outbound traffic is entirely optional and each item can be omitted: the licence heartbeat, company registration and the support assistant to the Ritzy License Server over HTTPS, SMTP for sending documents, the tax authority's endpoint where e-invoicing is in use, and the browser push services.

11 Deployment & updates

11.1 Traditional install

Deployment is a compiled bundle, not a source checkout. The build produces a deployment zip; extracting it over the install folder and restarting the process manager is the whole update. The installer installs PostgreSQL 18 when it is missing, sets up dependencies, writes the API's .env with the two database logins and a freshly generated secret, schedules a nightly pg_dump, registers the PM2 process and the Windows services, configures nginx and opens the firewall.

11.2 Docker

For a new standalone install the container bundle is the simplest path: two services, postgres (PostgreSQL 18) and app, each with its own volume. Updates are pull then up -d. A Ritzy desktop customer, whose data is in Firebird, is moved onto a stack with docker/migrate/: the companies are converted on the Windows side by the importer and restored into the stack as a pg_dump.

11.3 First run

A brand-new server has no database and no company registry, so there is nothing to log in to yet. The install therefore starts in first-run mode: a setup wizard — reachable only while zero companies exist, and additionally protected by the support password — creates the first company in the database (which the app has already created, with its schema and upgrades) and seeds the chart of accounts and defaults. Once a company exists the wizard is gone for good.

12 Backup & recovery

13 Integrations & external services

IntegrationDetail
ZATCA e-invoicing (Saudi Arabia)Phase-2 (Fatoora) clearance and reporting through the official SDK on the server. Invoices, cash memos, credit and debit notes are submitted on finalisation, with a locked invoice-hash and counter chain and a full submissions log. Enabled for Saudi installs only.
Bahrain customs manifestThe WCO-format sea cargo manifest, generated from Liner jobs and submitted to OFOQ/TFP with per-submission tracking. Requires the Liner module and the customer's own API keys.
EmailSMTP host, port, credentials and sender address are held per company in the database, so changing them is an application setting rather than a server file edit.
Web pushA standard VAPID key pair in the API's .env. Without it the notification inbox still works and only the push toggle is unavailable.
DocumentsWord .docx templates with merge fields for quotations and purchase orders; HTML and Handlebars templates, editable inside the application, for invoices and other billing documents. QR codes are generated server-side.
Data importCSV import for masters, opening balances and bank statements, with an AI-assisted column mapper.
PaymentsPayPal, on the vendor's licence server only — not something a customer install talks to.

14 AI capabilities

The AI features use Anthropic's Claude models and are strictly opt-in. They fall into two groups with different privacy profiles.

The management briefing is deliberately constrained: the model chooses which figures to show, but every number is computed by the server from the database. The model never produces a figure itself.

15 Sizing, performance & limits

DimensionPosition
Concurrent usersGoverned by the licence seat count rather than a technical ceiling. Ten to thirty concurrent users on the recommended profile is routine.
Companies per installSeveral — all in the one database, separated by Row-Level Security, and chosen at login. Bounded by disk and RAM, not by the application.
Tenants per hostOne VM per customer is the recommended isolation boundary; roughly six to eight 3 GB tenant VMs fit a 32 GB host.
Report performanceThe heaviest work is trial balance, general ledger and ageing across long histories. These are sort- and I/O-bound, so NVMe storage and enough RAM for PostgreSQL's cache (shared_buffers plus the OS file cache) matter far more than CPU count.
Known constraintsThe application is designed for one PostgreSQL server per customer and does not use read replicas or database scale-out. Plan capacity vertically and isolate per customer. (Standard PostgreSQL also opens the door to streaming replicas for standby and to managed PostgreSQL services, where a customer prefers them.)

16 Configuration reference

Everything environment-specific lives in one file, packages\api\.env, which is why the same build runs unchanged on Windows, on Linux and in a container. The keys that matter most:

# Paths and database
RITZY_ROOT=C:\Ritzy Solutions      # the BASE folder -- not ...\Ritzy
PG_URL=postgres://postgres:********@localhost:5432/ritzy      # superuser: makes the database ready on start
PG_APP_URL=postgres://ritzy_app:********@localhost:5432/ritzy # the login every request uses
RITZY_BACKUP_DIR=

# API
PORT=3001
JWT_SECRET=<generated at install>
JWT_EXPIRES_IN=8h
JWT_REFRESH_EXPIRES_IN=7d
CORS_ORIGIN=https://erp.example.com

# Optional features
ANTHROPIC_API_KEY=                 # AI data features
VAPID_PUBLIC_KEY=                  # web push
VAPID_PRIVATE_KEY=
VAPID_SUBJECT=
SOFFICE_PATH=                      # LibreOffice, for PDF rendering
FATOORA_HOME=                      # ZATCA -- Saudi installs only

# Licensing (inert unless enabled)
LICENSING_ENABLED=false
LICENSE_PUBLIC_KEY=
LICENSE_HEARTBEAT_URL=
Two settings people get wrong
RITZY_ROOT is the base folder that contains Ritzy\ and Backups\ — the application appends the rest itself, so it is C:\Ritzy Solutions, not C:\Ritzy Solutions\Ritzy. And PG_APP_URL must be the ritzy_app login, not the superuser: a superuser bypasses Row-Level Security, and the application refuses a second company on such a login.