← all work
CS-02In build · India first

Dhanavega

A digital lending platform for NBFC-class lenders.

Role
Led a team of six
Timeline
2025 to now
Stack
NestJSNext.jsPostgreSQL + RLSKeycloakPython + FastAPIRedisOpenAPI + RedoclyGrafana LGTMnext-intl

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

surfaces
Back-office · LOS / LMS / LCSPartner portalsCustomer web · en + ar
api
NestJS monolith · 72 controllers · 291 endpointsProduct builder · 42 admin controllers
kernels
Hash-chained audit logPermission tokens + maker-checkerRegulatory packs · fail closed
services
Workers · outbox + job state machinePython ML · document understanding
data
Postgres 15 · schema per module · RLSRedis 7MinIO / GCS
A modular monolith with a polyglot edge and a config-driven core.

Decisions that mattered

01

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.

02

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.

03

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.

04

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.

By the numbers

~176,000
lines across TS, SQL and Python
291
endpoints across 72 controllers
42
admin controllers of pure product config
2
generated client SDKs, TS and Python
41
architecture decision records
~118
database entities specified
2
locales shipped, en and ar
13
module OpenAPI specs, linted in CI