What it is
Dhanavega is a lending platform for regulated NBFC-class lenders, covering the full loan lifecycle: lead, onboarding, KYC, underwriting, sanction, disbursal, servicing, collections, closure. It serves lender staff, external partners like DSAs and co-lenders, and borrowers, across India and Saudi Arabia.
I led the second team of six and made the architecture calls. It is a NestJS modular monolith with a Node worker tier and a separate Python FastAPI service for document understanding, all wired through one OpenTelemetry kernel. Keycloak handles staff auth with TOTP always on. English and Arabic ship together, with Hijri calendars and RTL throughout.
The core idea is that a loan product is configuration, not code. The admin module alone has 42 controllers for credit policies, decisioning, scorecards, fee schedules, approval matrices and integrations, spanning product families from payday loans and BNPL to supply-chain finance, co-lending and Sharia-compliant ijarah.
Architecture
Decisions that mattered
Market rules fail closed
Regulatory differences between India and KSA live in market packs parsed from config: caps, thresholds, retention, calendars, Sharia settings, IFRS-9 staging. If a pack is missing an axis, the system refuses to run rather than defaulting to the wrong market's value. A repo-level lint fails the build if anyone hardcodes a market value in source.
An audit log that cannot be quietly edited
Every audited action appends to a hash-chained log through a declarative @Audited decorator. Alongside it: a separate access log, per-tenant RLS, and persisted decision artifacts so any credit decision can be explained after the fact. Regulators ask, and the answer is already stored.
The API is a shipped product
Thirteen module OpenAPI specs plus partner and webhook specs, all linted by Redocly in CI, generate two client SDKs, TypeScript and Python, published to private packages. Coverage floors are enforced per tier, and CI is configured to fail rather than silently skip when test infra is missing.
One monolith, two languages, one Python service
Most of the platform is one NestJS monolith because a lending core wants transactions, not network hops. Document understanding is genuinely different work, so it is a separate Python FastAPI service. Both speak the same OpenTelemetry dialect into a local Grafana stack.