Why I built three full-stack products back to back
Most of my client work up to this point had been WordPress: plugins, themes, marketing sites for AI and SaaS companies. Good work, real clients, real production reach — but it doesn't fully show what I can do on a modern full-stack product, because a WordPress theme and a two-sided SaaS dashboard exercise almost none of the same muscles. So I set out to build three products back to back: two web apps and one mobile app, each picked to force a different set of decisions.
Picking three problems that don't overlap
It would have been easy to build three CRUD apps with slightly different branding. Instead I picked problems that share almost no architecture:
- Reppod — a two-sided coaching SaaS on Next.js 16 and MongoDB. The hard problem here is two roles sharing one auth system, an invite flow linking a trainer's client record to an account that doesn't exist yet, and AI-generated workout plans that a trainer can actually trust.
- WorkNavo — a multi-tenant client-ops platform on the MERN stack (React, Express, MongoDB). The hard problem is one user belonging to several organizations with different roles in each, and making sure a Project Manager can never see what a client is billed.
- SecurePay — a multi-currency digital wallet: React Native frontend, six FastAPI microservices on the backend. The hard problem is fraud screening on the critical path of money moving, with one service's slowness never allowed to block another's.
Three different frontends (Next.js, Vite + React, React Native), three different backend patterns (monolithic Next.js API routes, an Express REST API, six independent FastAPI services), two different primary languages (TypeScript and Python). That was deliberate — the point was breadth under real constraints, not three variations on the same stack.
What actually transferred between them
The tech stacks didn't overlap much, but the same three decisions showed up in every single one:
Who can see what, enforced once, not everywhere. Reppod's trainer/client split, WorkNavo's org-scoped roles, and SecurePay's email-verified-only wallet creation are the same underlying problem: authorization has to be checked in one place — middleware, a serializer, a dependency — not sprinkled across every route handler where it's one missed if away from a real bug.
A slow or broken dependency shouldn't take down something unrelated. SecurePay's notification service is fully decoupled from transfers via RabbitMQ so a flaky email provider never blocks money from moving. It's the same instinct as making sure AI-generated content in Reppod lands as a draft, not an auto-committed action — isolate the part that can degrade from the part that can't afford to.
Write the constraint into the schema, not the hope. Whether it's a Zod schema constraining what an AI tool call can return, a Mongoose schema scoping every document to an organization, or a Pydantic model validating a transfer request, the pattern that held up under pressure was always: make the invalid state impossible to represent, rather than trusting a handler to remember to check.
The part that doesn't show up in a demo
Building three real products back to back — not tutorials, not toy apps — surfaces problems tutorials skip entirely: what happens when a trainer deletes a client who has 40 logged sessions, what a Project Manager should see in a report versus an invoice, what a wallet does when the fraud service times out mid-transfer. None of that is interesting to write a blog post about in isolation, but it's the actual work, and it's the reason "I built three apps" is a more honest signal of ability than "I know React, Node, and Python" ever is on its own.
What I'd do differently
If I were starting over, I'd write the seed/demo data scripts for each project on day one instead of near the end — Reppod and WorkNavo both ended up with seed:demo scripts that generate realistic data, and having that from the start would have made manual testing faster throughout, not just at the end when I needed screenshots. I'd also decide the deployment target (Vercel, Railway, Docker) earlier — SecurePay's Docker Compose setup was designed after most of the service split was already done, and a couple of environment-variable assumptions had to be untangled retroactively.
None of these are dramatic lessons. That's sort of the point — the value of building three real products wasn't a single big insight, it was accumulating the unglamorous judgment calls (when to split a service, when a draft beats an action, when a schema should reject input outright) that only show up when you actually ship something end to end.