I reviewed my own SaaS starter kit like a hostile buyer before selling it. It didn't all pass
Most starter kits are sold on a README promise. A list of green checkmarks, a slick landing page, and the quiet assumption that you'll never actually audit the code before your card gets charged.
I didn't trust my own kit enough to do that. So before I put a price on it, I sat down and reviewed NuxtForge the way a skeptical buyer would: fresh clone, README only, no mercy, no memory of having written any of it. It did not all pass. This is what broke, what I fixed, and what's actually tested now - because a kit that hides its history will hide its bugs.
What it is, in one paragraph
NuxtForge is a decoupled Nuxt 4 + NestJS 11 SaaS foundation in TypeScript. Not a monolith - a real API (NestJS, Prisma 6, PostgreSQL 17, Redis, BullMQ, S3/MinIO, Stripe, Resend) and a separate SSR frontend (Nuxt 4) behind Nginx, all wired together with Docker Compose.
The idea is boring on purpose: give you the undifferentiated plumbing every SaaS rebuilds from scratch - auth, tenant isolation, config, billing hooks, an admin shell - already built and tested, so you start on your product instead of on npm init. That's the pitch. Now the part nobody shows you.
The hostile review: four things that broke
I cloned it into an empty folder, opened only the README, and started following it like someone who had just paid for it.
1. The test runner was dead.
There was a test script in package.json - and no test configuration behind it. The suite couldn't even run. A kit that advertises "tested" with a test command that does nothing is exactly the kind of thing you only discover after you've paid.
Fixed: a real suite now covers tenant isolation, webhook idempotency, auth, and permissions - including the failure paths, because "denies when it should" matters more than "allows when it should."
2. A fresh clone didn't boot.
Four separate bugs on the happy path, each individually small, collectively fatal:
- In-container service hosts pointing at
localhostinstead of the compose service names. - A dev command the container entrypoint quietly ignored.
- Env values silently corrupted by trailing
#comments that got parsed as part of the value. - An SSR self-fetch loop that spiralled into an out-of-memory 504.
None of these show up in a screenshot. All of them show up the first time a buyer runs docker compose up.
Fixed: a clean clone now boots to a working admin login.
3. Nobody could log in.
The seed created the default tenant - but no user. A login page with no account behind it. You'd type credentials, get a correct "invalid credentials," and have no idea why.
Fixed: a dev admin is now seeded (and refuses to seed in production, where the first admin is created via an explicit step).
4. The e2e suite caught a real one.
Once the end-to-end harness existed - real Postgres and Redis in throwaway containers, no mocks - it immediately caught an async tenant-context slip: a tenant-scoped query resolving after its context had already exited. That's precisely the failure the whole multi-tenancy architecture exists to prevent, and it got caught before any buyer could ever meet it.
That last one is the whole argument for writing the tests. The architecture was designed to make cross-tenant leaks impossible, and the tests still found a path where it could slip. Design intent is not proof. Tests are.
The receipts
Here's what runs now, on every push, in CI. These are the actual suites - you get every one of these files in the zip:
$ npm test
PASS tenant.extension (structural isolation) 9
PASS auth/permissions.guard 6
PASS security/webhooks (Stripe) 5
PASS auth/token-duration 3
PASS auth/password.service 2
Test Suites: 5 passed, 5 total
Tests: 25 passed, 25 total
$ npm run test:e2e # real Postgres + Redis via Testcontainers
PASS App e2e
โ logs in, hits a protected route, refreshes, hits it again
โ hides tenant A data from a query run in tenant B context
โ accepts a signed webhook once, de-duplicates the replay
Tests: 3 passed, 3 total
No mocks in the e2e path. Tenant isolation and webhook idempotency are proven against actual databases, not a stubbed client that always agrees with you.
Three things I actually cared about getting right
Structural multi-tenancy - not a where clause you have to remember
A Prisma extension detects tenant-scoped models and forces tenantId onto every read and write: filters merged onto finds, cross-tenant updates that simply can't match a row, and - critically - it's fail-closed. A scoped model with no tenant in context throws, rather than silently returning everything. You add a model with a tenantId column and it's isolated the moment you declare it.
A Stripe webhook that does the three hard things
- Signature verified against the raw body (not the parsed one).
- Idempotent on the event ID in Redis, so a retried event never double-fulfills.
- Server-authoritative amounts - re-verified, never trusted from the payload.
These are the three places sloppy kits quietly lose money, and they're the first thing I'd check before trusting anyone else's billing code.
Config that refuses to start wrong
Every env var is validated at boot with Joi. Feature flags flip their own secrets from optional to required - you can't half-enable billing or email and discover it in production. If the config is wrong, the API doesn't start. Fail-closed beats fail-confusing.
What it is NOT
Being honest about this is the point, so:
- It's plumbing, not a finished product. The frontend is an admin shell and a login. There are no marketing pages and no pre-built UI themes. Your product's screens are yours to build.
- Billing is wired and secured, not designed. The webhook, idempotency, and security baseline are done. Your plans and checkout UI are not.
- It's opinionated. Decoupled API + SSR frontend, TypeScript everywhere, PostgreSQL, Node 24. If you want a one-folder monolith, this isn't it - and that's deliberate.
If you were hoping to buy a ready-to-sell app, this isn't that. If you were hoping to skip three weeks of auth-tenancy-billing groundwork and the security mistakes that come with rushing it, this is exactly that.
The stack
Nuxt 4 ยท Vue 3 ยท NestJS 11 ยท TypeScript ยท Prisma 6 ยท PostgreSQL 17 ยท Redis ยท BullMQ ยท Stripe ยท MinIO/S3 ยท Resend ยท Docker Compose ยท Nginx ยท Node 24 ยท GitHub Actions CI.
Where it landed
NuxtForge is a one-time purchase - โฌ89 for a single developer, โฌ199 for up to five seats / client work - sold through a merchant of record so VAT is handled at checkout. It's at nuxtforge.com.
But the reason I wrote this isn't the price. It's the process. If you're building or buying a starter kit, review it like a hostile buyer before it matters - run its tests from a clean clone, boot it on a machine that isn't yours, and make it prove the things its README claims. A kit that can survive that is worth building on. One that can't will cost you the three weeks it promised to save, plus interest.
Happy to answer anything about the multi-tenancy approach or the webhook handling in the comments.
Comments
No comments yet. Start the discussion.