Case study
Village Finance
Multi-tenant lending and collections platform for village finance lines
- Role
- Full Stack Developer
- Status
- In testing · backend live on Google Cloud
- Year
- 2026
- Category
- Web platform
01
Overview
Village Finance runs the daily and weekly collections of small lending businesses. An owner sets up collection lines, slots and villages, issues loans to customers and closes the books every week. Field collectors work their assigned slots from a phone. A platform console onboards each client business as an isolated tenant. The backend is live on Google Cloud Run; the Android app is at version 1.2.10 in internal testing.
- 3
- applications: mobile, API, console
- 20+
- tenant-scoped tables
- 460+
- automated frontend tests
02
Problem
Village finance lines involve many customers, collectors and villages, with money moving every day. Owners need books that close cleanly every week and can be corrected when they do not. Collectors need a phone-first tool that stays fast on a weak mobile connection and cannot be shared between phones. And because one platform serves many businesses, a mistake in isolation would expose one client's customers to another.
03
Product
- Collection lines with recurring slots and cities, plus a 30-day trash with restore and scheduled purge
- Customers with photos, location and guarantor details; loans with installment schedules, payments, voids and carry-forward
- Weekly close and reopen with an older-history boundary, per-slot breakdowns and a printable A4 ledger PDF
- Capital investments and withdrawals, partner contributions, expenses, chit-fund schedules, quick-loan presets and leave logs
- Team management with one-phone-per-collector device binding, device reset and a member home with today's summary and dues
- Passwordless setup: accounts receive an emailed one-time code with rate limits, and passwords are only ever set through it
- Platform console to onboard, rename, suspend, restore and soft-delete tenants and manage their admin logins
04
My role
- Sole developer across the Expo mobile app, the Spring Boot API, the React console, the data model and the cloud deployment
- Designed the multi-tenant security model and the admin, member and platform role boundaries
- Built the collections, loan, weekly-close and capital domains and the ledger PDF export
- Set up Docker packaging, Cloud Run deployment with Secret Manager, Neon PostgreSQL and Cloudflare R2 storage
05
Architecture
Clients
Expo mobile app
React Native with Expo Router, React Query and Zustand. Admin and collector route groups, FlashList and Reanimated collections grid.
Platform console
React and Vite, no extra dependencies. Passwordless sign-in for platform operators.
API
Spring Boot 3 REST API
Java 17. Stateless JWT with BCrypt, admin and member roles per endpoint, separate platform token type.
Domain services
Lines, slots, customers, loans, installments, weekly close, capital, partners, chitty, leaves, quick loans.
Data & storage
Neon PostgreSQL
Serverless Postgres via JPA and Hibernate. Connection pool sized for Neon auto-suspend.
Cloudflare R2
Private bucket for customer photos. Presigned upload and download URLs; bytes never pass through the API.
Services
Email (Resend)
One-time codes and password-changed notices through a pluggable mail sender.
Scheduled jobs
Trash purge after the 30-day retention window.
Delivery
Docker + Cloud Run
Two-stage image, non-root runtime, secrets from Google Secret Manager, revision-based rollback.
EAS builds
Internal APK previews and production AAB for the Play internal track.
06
Technical implementation
- Every tenant-scoped query filters on the tenant id carried in the signed JWT; the request body and parameters can never name a tenant
- Device binding service with distinct wire codes for a refused device versus a released one, so the app shows the right recovery panel
- Device and OTP audit events written in their own transaction so a refused sign-in still leaves an audit row
- Installments for a screen loaded in a single query instead of one per loan; large ledger responses gzip-compressed
- Ledger PDF built as A4 landscape HTML with pure, unit-tested pagination and native rendering isolated in a hook
- Production safety check that refuses to boot outside development with the committed JWT secret or without a real mail sender
- Frontend layered one way, app to features to services to storage, with a single HTTP client translating server errors into typed app errors
07
Key engineering challenges & solutions
- 01
Keeping clients isolated on one database
- Challenge
- Many businesses share one schema, so any endpoint that trusted a tenant id from the request could leak another client's customers.
- Solution
- Tenant identity comes only from the signed token and is applied in every tenant-scoped query. Creating a tenant requires a separate platform token type that no regular account can hold.
- 02
Collectors sharing phones
- Challenge
- A collector's login on a second phone breaks accountability for cash collected in the field.
- Solution
- Bound each account to one device with an admin-side reset and a break-glass switch, and recorded every bind, refusal and reset in an audit trail that survives the failed sign-in's rollback.
- 03
Photos over a village mobile connection
- Challenge
- A pull-to-refresh on a slot with hundreds of customers re-downloaded every photo through the API.
- Solution
- Moved photos to a private R2 bucket with presigned URLs and cached signed URLs so unchanged photos stay cached on the device.
- 04
A serverless database behind a serverless API
- Challenge
- Neon suspends idle databases and Cloud Run cold-starts, so naive connection settings produced timeouts on the first request.
- Solution
- Capped the connection pool, set a 30-second connection timeout, and trimmed the container image and SDK clients to cut cold-start time.
- 05
Weekly books that can be corrected
- Challenge
- Closing a week is irreversible on paper, but real collections get corrected after the fact.
- Solution
- Weekly close stores opening balance, given, collected, expenses and capital per slot, and the latest week can be reopened, with an older-history boundary protecting settled books.
08
Quality & evaluation
- 460+ frontend tests that drive screens through real repositories over in-memory storage, so a tap is verified from hook to persisted state
- Type checking and the test suite run on every push through Git hooks
- Idempotent schema maintenance at startup for changes Hibernate cannot apply, mirrored as a reviewed SQL migration
- Reversible destructive actions throughout: trash and restore for lines, reopen for weeks, suspend and restore for tenants
09
Technology stack
- React Native (Expo)
- TypeScript
- Expo Router
- React Query
- Zustand
- Java 17
- Spring Boot 3
- Spring Security (JWT)
- PostgreSQL (Neon)
- Hibernate / JPA
- Cloudflare R2
- Docker
- Google Cloud Run
- React + Vite
- Jest
10
Outcome
The API is live on Google Cloud Run with Neon PostgreSQL and Cloudflare R2, and the Android app is in internal testing at version 1.2.10 ahead of its Play Store release.
11
Links
No public link is available for this project.