In a multi-tenant system, one forgotten tenant filter can expose another organisation's records. The failure is dangerous because it often stays invisible during development when only one organisation exists.
TaskForge
A multi-tenant SaaS workspace with tenant-safe authorization, real-time collaboration, and background processing — designed so organisation boundaries are enforced by architecture, not developer memory.
Product thinking, engineering decisions, system structure, and implementation.

The problem
What made this worth solving?
Project-management CRUD is straightforward until multiple organisations share the same database and real-time infrastructure. At that point, every read and write becomes an isolation problem: one missed tenant condition can expose another organisation's data, while notifications and background work must remain reliable without slowing request paths.
Engineering approach
How the system was shaped.
The system is split into a Next.js frontend and NestJS API so the backend can be exercised independently. Organisation membership and role resolution happen in the request guard chain before handlers execute. PostgreSQL remains the durable source of truth, Redis supports caching and rate limiting, Socket.IO handles real-time updates, and BullMQ moves email and notification fan-out to workers outside the request path.
- 01Next.js 15
- 02NestJS
- 03TypeScript
- 04PostgreSQL
- 05Redis
- 06Socket.IO
- 07BullMQ
- 08Stripe
Hardest technical problem
The part that needed real engineering.
Organisation membership and role enforcement were moved into the request guard chain so authorization resolves before handlers execute. A route therefore cannot widen access simply because an individual query forgot a tenant condition.
Outcome
What the work produced.
The product is deployed with a seeded demo account, allowing the complete organisation, board, role, and live-notification workflow to be explored without local setup.
Looking back
What I would change today.
The frontend still owns hand-written copies of several API types, leaving room for contract drift. A stronger iteration would generate shared contracts from the API schema so incompatible backend changes fail during development rather than at runtime.
Design the behaviour. Engineer for what happens next.