The challenge
Indian landlords track rent, bills, leases and repairs across notebooks and chat threads, with no safe way to let someone else handle part of the work.
The approach
Built a server-component-first Next.js 15 app with MongoDB/Mongoose, Auth.js v5 sessions, Zod validation at the service boundary and edge middleware for routing and session gating, plus a super-admin delegation model for scoped sub-accounts.
The outcome
Properties, units, tenants, leases, rent cycles, utility bills, payments, receipts and maintenance in one place, with parts of the workload delegated to sub-accounts without sharing the owner's account.
How HomeFlow is put together
A server-component-first Next.js 15 app on Vercel. Requests pass a cookie-only edge gate, then a strict route → action → service → repository → model stack, with authorisation, property scoping and audit enforced in the service layer. Reads are designed around a slow serverless cold connect.
- domain modules
- 29
- invariants held by unique indexes
- 10
- cold DB connect kept off the hot path
- ~2.8 s
- layers, none skippable
- 5
01 · Clients
client
Owners & staff
client
Tenants
self-service portal
02 · Edge
gateway
Edge middleware
session-cookie gate, no DB
worker
Vercel Cron
daily rent generation
03 · Next.js server
service
Routes & actions
RSC · server actions
streamed Suspense sections
service
Auth.js v5
Google, JWT sessions
04 · Domain
service
Services
Zod · authz · audit
29 domain modules
service
Org-scoped repositories
orgId + property scope
05 · Data & services
database
MongoDB
Atlas · Mongoose 8
records + private file bytes
database
Data Cache
Vercel
tagged view models
third-party
Resend
optional email
11 connections
- Owners & staff→Edge middlewareHTTPS
- Tenants→Edge middlewareHTTPS
- Edge middleware→Routes & actionscookie present
- Routes & actions→Auth.js v5claims
- Routes & actions→Data Cacheread view models
- Routes & actions→Servicesserver actions
- Vercel Cron→ServicesBearer CRON_SECRET · async
- Services→Org-scoped repositories
- Org-scoped repositories→MongoDB
- Services→Data Cacherevalidate tags · async
- Services→Resendnotify · optional
Request flow · Creating a bill (a server action)
- 1Edge middleware lets the request through only if a session cookie exists; it never touches the database.
- 2The action resolves the actor from the session and hands the input to the bill service.
- 3The service Zod-parses the input, asserts the permission and checks the property is in the actor’s scope, then inserts through the org-scoped repository and records an audit entry.
- 4Dashboard cache tags are revalidated and a typed ActionResult returns; internal errors never cross to the browser.
Design decisions
- Invariants live in unique indexes
- Double-billing a month, duplicate receipts and duplicate agreement numbers are prevented by ten database indexes, not read-then-write checks.
- Built for a slow cold start
- A cold serverless instance pays ~2.8 s to reach Atlas, so the app shell answers from cookie claims and Data Cache and only opens a connection when it must.
- Cache keys carry scope
- Dashboard view models are cached per property scope, so a sub-account never reads the owner’s whole portfolio from a shared entry.
- ID documents: derive, never grant
- Access to a tenant’s ID is recomputed per request from an active lease, logged before bytes are served with no-store, and refused as a 404. Only the last four digits of an Aadhaar number are kept.
Trade-offs & known limits
- Account type is per login: someone who is both staff and a tenant currently needs two email addresses.
- File bytes are stored in MongoDB documents, which keeps authorisation in one place but bounds file size by the document limit.
Have something similar in mind?
I'm open for freelance and contract work — let's talk about what you're building.