The NestJS SaaS boilerplate for multi-tenant B2B apps
Keelmint's API is a NestJS 11 application on PostgreSQL and Prisma that already does the hard, boring parts of a B2B backend, and proves it with more than 700 end-to-end tests against a real database.
Authorization you declare, not re-implement
Every route states what it needs. A controller for an organization's data takes @Entity(), and each
route the permission it checks; the permission catalog is generated from the code, so a role editor always lists
what really exists.
- Built-in roles in a hierarchy: platform admin, organization admin, unit admin, user, plus custom roles per platform or per organization.
- Role assignments are organization-wide or scoped to a unit and its sub-units.
- Nobody can grant or remove a permission they do not hold; role edits and invitations are checked the same way.
- A missing permission answers
403 PERMISSION_REQUIREDwith the permission's name, so the client can explain it.
Tenant data that fails closed
Mark a Prisma model // @tenant and a Prisma extension scopes every query on it to the active
organization. Without one, the query is refused instead of returning everyone's rows. Records that belong to a
person are // @owned: callers see their own, and admins see the people in their reach. The details are
on the multi-tenancy page.
Billing per organization
Stripe subscriptions belong to an organization, not a person. Plans carry limits (members, units, or your own counters) and feature flags; a free plan and a no-card trial are built in, along with Checkout, the Customer Portal and idempotent webhooks. Enforcing a limit in your own code is one call inside your transaction:
await PlanLimits.assertWithin(tx, organizationId, 'projects');
Leave billing out of the product config and nothing is limited and no Stripe keys are needed.
Security details already handled
- Short-lived JWT access tokens and rotating refresh tokens in an HttpOnly cookie, with replay detection.
- argon2 password hashing, email verification, password reset that does not reveal which emails exist.
- TOTP two-factor with recovery codes, encrypted secrets and a shared failure budget.
- Rate limiting, Helmet, strict request validation, and an append-only audit log written in the same transaction as the change.
- Soft delete for users, organizations, roles and memberships, enforced by the data layer.
Background work without new infrastructure
pg-boss runs the job queue on the same PostgreSQL database. Every email goes through it, inserted in the request's
transaction, so a request never waits on SMTP and an outage delays mail instead of failing requests. Live in-app
notifications travel over server-sent events backed by Postgres LISTEN/NOTIFY: no Redis to run.
Built to be operated
- Environment validated at boot with zod: the API lists every problem and refuses to start.
- Structured JSON logs with a request id on every line and in every error response.
- One error format,
{ statusCode, code, message, details?, requestId }, with stable codes. - A Swagger document generated from DTOs that implement shared interfaces, so it cannot drift.
- A Docker image and a Compose file with Caddy for HTTPS: one VPS, one command.
Read the code's documentation first
The introduction lists every feature and what is not included, and the deploy guide shows production step by step. The client side is covered on the Angular page.