Enterprise · Our own product

A SaaS ERP built the hard way round

We had delivered enough accounting systems to know why they fail: a schema that fits the first module and nothing after it. So we designed the full ERP data model first, then shipped the accounting product on top of it — with a separate database for every customer company.

  • A database per tenant
  • ZATCA signing in the ledger
  • Arabic-first, both languages

Project at a glance

Client
Valeur X (in-house product)
Sector
SaaS · Accounting & ERP
Delivered
2026 — in production
Services
Custom software, Cloud, Web

The situation

Two ways an ERP product dies

We started this because both failure modes were visible in systems we had been asked to rescue. The architecture is a direct answer to them.

  1. 01

    Build everything, ship nothing

    Teams attempt eleven modules at once and have nothing sellable two years later. The scope has to be narrow while the foundation stays wide.

  2. 02

    Ship fast, rewrite later

    An accounting product succeeds, then inventory cannot be added because the schema never anticipated it, and the rewrite costs more than the original build.

  3. 03

    Shared tables, shared blast radius

    Most SaaS puts every customer in one database with a tenant column. One bad query, one migration mistake, and the exposure is everybody's data at once.

  4. 04

    Compliance treated as an add-on

    ZATCA bolted on beside the ledger means two sources of truth, reconciliation work every month and a painful audit.

What we built

The architecture behind the product

Decisions taken before the first screen was designed, because none of them can be retrofitted cheaply.

  • One database per tenant

    Each customer company is provisioned its own database, so an export, a restore, a private deployment or a data-residency requirement is a normal operation rather than a project.

  • A shared control plane

    Identity, subscriptions, provisioning and fleet orchestration live in one place, while business data never leaves the tenant's own database.

  • The full ERP data model, day one

    Every journal line carries a cost centre and project from the start, and products are typed as stockable long before inventory ships — so later modules are additions, not migrations.

  • A posting engine at the centre

    Every business event — an invoice, a bill, a payment, a depreciation run — produces its journal entry through one engine, which is what keeps the ledger trustworthy.

  • ZATCA inside the flow

    Invoice XML is signed with the company's certificate and cleared as part of issuing the invoice, with the QR code and the audit trail stored against the same record.

  • Arabic-first interface

    Built bilingual from the first component, with right-to-left layouts designed rather than mirrored, because an accountant should not be the one adapting.

What changed

A platform that can take the next module without a rewrite

The accounting product is complete and in production; inventory, assets, projects, payroll and CRM sit on the same schema. The cost of that decision was paid once, at the beginning.

  • A new customer is provisioned with their own database automatically
  • Adding a module means new tables, not a schema rewrite
  • A tenant can be exported or moved to private hosting on request
  • Compliance output is generated from the ledger, not reconciled against it
  • 41

    Modules on one shared data model

  • 195

    Tenant migrations, applied per company

  • 66

    API route groups behind one gateway

Built with

Deliberately boring where it matters: a relational core, explicit migrations, and no framework that would be hard to hire for.

Frontend
  • Next.js
  • React
  • TypeScript
  • Tailwind CSS
  • React Query
  • next-intl
Backend
  • Node.js
  • Express
  • Sequelize
  • Umzug migrations
  • Zod
Data & security
  • PostgreSQL
  • Argon2
  • JWT
  • Helmet
  • Rate limiting
Compliance & documents
  • XML signing
  • node-forge
  • QR codes
  • Puppeteer PDF
  • ExcelJS

Thinking about building a product, not a project?

Multi-tenancy, provisioning and a data model that survives the second module are decisions best made before the first one. We have made them.