Context
Landlords, tenants, contractors, and admins all work on the same records, such as a lease or a maintenance request, but each may only see and do certain things, and one landlord’s data must never leak into another’s.
My role
My main work was designing the multi-tenant architecture and the infrastructure, and building the core domain: tenants, leases (renewal, termination, consistent UTC date handling), properties and media, maintenance, and the permission policies.
Architecture
- Every API route requires a logged-in user unless it’s explicitly marked public.
- Permissions are built per user with CASL and enforced by a guard plus a policy class per resource: leases, properties, units, tenants, transactions, media, users, and more.
- Admins are global; landlords, tenants, and contractors are scoped to their own tenant, and a database plugin isolates each tenant’s data.
- A scheduled job moves records between statuses over time, such as marking payments overdue.
Design decisions & trade-offs
- Permissions as policies, not per-endpoint checks. Access rules live in one policy class per resource that every request goes through, instead of being re-checked by hand in each handler. Trade-off: the permission model has to be designed carefully up front, because every feature depends on it.
- Tenant isolation in the data layer. Scoping happens in a database plugin rather than in each query, so a forgotten filter can’t leak another tenant’s data. Trade-off: cross-tenant work (admin views, reporting) has to bypass it explicitly.
Stack
NestJS · TypeScript · MongoDB · CASL · JWT · Next.js