Open source
ack-nestjs-boilerplate
- Ongoing
- MIT
- v9.1.0
A NestJS starter for teams that would rather not rebuild authentication, permissions and multi-tenancy for every new service. Every route passes the same ordered chain of middleware and guards, so a new feature gets throttling, auth, policy checks and tenancy without extra code.
- Where
- Open source, my own project
- Role
- Sole maintainer. I review contributor pull requests.
- Dates
- Jan 2022 – Present
- Stars
- 700+ on GitHub
- Licence
- MIT
- Release
- v9.1.0
- Stack
- NestJS 12, TypeScript 6, Prisma 6, MongoDB, Redis, BullMQ, Zod 4
- GitHub stars
- 700+
- unit coverage
- 100%
- GitHub Actions workflows
- 9
- guides in docs/
- 32
01Context and my role
I started ack-nestjs-boilerplate in January 2022 and have maintained it on my own since then. It is my own project, not employment or client work. It is released under the MIT licence, has 700+ stars on GitHub, and the current release is v9.1.0. My own work projects started from it: Kadence K-OINS, Kadence Internal Tools, Korner and Field Operation Platform.
02Focus
ack-nestjs-boilerplate is a modular NestJS starter. It exists so a team does not rebuild authentication, permissions and multi-tenancy for every new service. A few choices shape it: stateful authentication and authorization, a repository layer between the domain code and Prisma, configuration through environment variables in the Twelve-Factor style, native ESM, and tenancy by workspace, with projects scoped to a workspace.
- Enterprise apps: roles, CASL policies and an activity log.
- Auth services: JWT, Google and Apple sign-in, and TOTP two-factor.
- Mobile backends: a REST API with social sign-in, device tracking and push notifications.
- Multi-tenant SaaS: every user belongs to a workspace, and projects stay scoped to it, with invites and join requests.
- Startup MVPs: auth, workspaces, notifications and file upload already wired.
03Features
Authentication
JWT signed with ES256 (access) and ES512 (refresh), Google and Apple sign-in, one-time passwords, TOTP two-factor with backup codes.
Sessions
Redis-backed sessions that can be revoked. A stolen token dies on the next request once the session is gone.
Permissions
A role is not enough. Each person also has abilities: who may invite, who may read a project, who may change a flag. Those abilities travel with the product, not with one screen.
Feature flags
Turn a feature on for a share of people without a new release. Rollout is a percentage, not a deploy.
API keys
Machine access with the same protection as a signed-in person, so a job or a partner can call in without a password.
Multi-tenancy
Every user belongs to a workspace. Projects stay scoped to it, with invites, membership and join requests.
Database
Prisma on MongoDB (a replica set, so transactions work), a repository layer, Redis cache, offset and cursor pagination.
Storage
Files reach S3 two ways: presigned URLs for GET, upload and multipart parts, so the bytes skip the API, or multipart upload through the API, which then writes to S3.
Background jobs
BullMQ takes email, push and logs off the request. The response returns; workers retry the slow work.
Notifications
In-app and silent notifications, email through AWS SES, push through Firebase Cloud Messaging.
Observability
Pino logs each request. Sentry records failures. Bull Board shows the BullMQ queues. Activity logs, admin and per-workspace analytics, and health checks show what ran.
Operations
Rate limiting with a sliding window in Redis, Helmet, Zod request contracts, i18n, OpenAPI 3.1 docs, optional HashiCorp Vault for secrets.
CI/CD
GitHub Actions for lint, tests and releases to AWS EC2, AWS ECS or any server over SSH, with Docker.
Tooling
Native ESM on Node.js 24 and TypeScript 6, SWC builds, Vitest.
AI
I wired Claude Code into the repo:
AGENTS.md, skills (ack-build,ack-doc,ack-plan,ack-harness,ack-pr,ack-review,ack-spec) and agents (coder, debugger, explorer, harness, reviewer, tester, writer), so an agent can change the starter the same way I do.
04Why start from it
- My own backends start from it: Kadence K-OINS, Kadence Internal Tools, Korner and Field Operation Platform started from this code. Their auth and API keys came from its shared layer.
- Integrations stay off until set: AWS S3 and SES, Firebase, Sentry, and Google and Apple sign-in are optional. A blank line in
.envleaves one unset and the API still boots. A route that needs an unset one answers 404. - Docs from the route schemas: Swagger UI and
generated/swagger.jsonare built from the same route schemas, in OpenAPI 3.1. Swagger is off in production. - One guard order: a route declares only the protection decorators it needs, and they always run in the same relative order.
- 32 guides: installation, configuration, every core module and release each have their own guide in
docs/. - Clone, tweak, run: you do not write a report or a spec to get a backend. Auth, tenancy, jobs, storage and docs are already there. Clone the repo, set the env you need, and use it.
- AI workflow included: Claude Code is wired in:
AGENTS.md, skills and agents, so an agent can change the starter the same way I do.
05How it works
A request lands on one of the route groups: public, user, admin, system or shared. Workers sit on a separate processor router. Before any handler, the pipeline is the same.
- Middleware, in order: request ID, request log, Helmet, body parser, CORS, URL versioning, response time, language, workspace context, then compression.
- Throttle: default then per-route, sliding window, counters in Redis.
- Route guards, in order: API key, JWT checked against the Redis session, feature flag, user, workspace and membership, project and membership, role and CASL policy, terms accepted. Nest runs the protection decorators bottom-up, so that is the order they execute.
- Validation: a Zod pipe on the body.
- Interceptors: timeout and actor.
- Handler: controller, then domain service, then repository, then Prisma, then the MongoDB replica set, with Redis cache beside it. The response returns from there.
- Side effects: email, FCM push, in-app and silent notifications and activity logs leave as BullMQ jobs. Processors send them after.
- Errors: global exception filters. Sentry gets the failure, and the client always gets the same error shape.
06Decisions and trade-offs
Business
Feature flags with percentage rollout
- Why
- A feature can reach a share of people without a new release.
- Cost
- Rollout identity depends on guard order. Run the flag before sign-in and a percentage targets an anonymous header, not an account.
Infrastructure
Asymmetric JWT keys (ES256 access, ES512 refresh)
- Why
- Another service can verify a token with the public key and never holds the signing key.
- Cost
- If JWKS is unreachable, signed-in users get 503, not a token error. Local compose has to run that endpoint too.
Sessions in Redis behind signed tokens
- Why
- Every request checks the signature and the Redis session, so revoking a session takes effect on the next request.
- Cost
- Redis sits on the auth hot path. Redis down, and a valid token still fails.
MongoDB as a replica set, even locally
- Why
- Prisma needs a replica set for transactions, so local development matches production.
- Cost
- A laptop runs a replica set. A single-node Mongo from a tutorial will fail transactions.
Prisma for the repository pattern
- Why
- Domain services talk to a repository, not to the database, so a later database change is a repository swap, not a rewrite of every service.
- Cost
- A later database swap is still a Prisma schema rewrite. Only the domain services stay untouched. Until then every query is Prisma's generated client.
Presigned S3 uploads
- Why
- The client sends and fetches files straight to and from S3, so the bytes never pass through the API.
- Cost
- Untrusted bytes land in the bucket. The API cannot reject a file by looking at it. Access and expiry live on S3.
Async work on BullMQ
- Why
- Email, push and activity logs must not hold the response. A worker retries if the process dies.
- Cost
- HTTP 200 can land before the email exists. If workers are down, the user sees success and the queue waits.
Other
Guards as a fixed chain
- Why
- Every route passes the same ordered checks, so a new feature inherits throttling, auth, policy, terms and tenancy without extra code.
- Cost
- You cannot reorder one check for one route. Put the flag below JWT and rollout follows an anonymous id, not the signed-in user.
Observability
- Why
- A request, a crash and a queued job need different views. Pino logs the request. Sentry records the crash. Bull Board shows the queue.
- Cost
- Three UIs to operate. Skip Sentry and crashes stay in the log. Skip Bull Board and a stuck job is invisible.
Firebase Cloud Messaging for push
- Why
- One push path covers iOS, Android and the web. The send runs on a BullMQ job so the request does not wait on FCM.
- Cost
- Push is Google's availability. No FCM key, and those routes 404. iOS, Android and web all go dark together.
AI workflow in the repo
- Why
- I wired Claude Code into the starter:
AGENTS.md, skills (ack-build,ack-doc,ack-plan,ack-harness,ack-pr,ack-review,ack-spec) and agents (coder, debugger, explorer, harness, reviewer, tester, writer), so an agent follows the same workflow I use to change the project. - Cost
- The harness is Claude Code's. Cursor or Codex does not run those skills until someone maps the files.
07Testing and delivery
- Tests: a Vitest unit suite in
test/unit/, 705 spec files. A 100% coverage threshold on branches, functions, lines and statements. Controllers, repositories, modules, processors and generated code sit outside it. - GitHub Actions, 9 workflows:
linterruns on every pull request.test-unit,test-integration,test-e2e,release-version,release-with-aws-sdk-ec2,release-with-aws-sdk-ecsandrelease-with-sshstart by hand.staleruns daily. - Docker image and local compose with a MongoDB replica set and optional Vault.
08Stack
- Runtime and language
Node.js 24
TypeScript 6
- native ESM
SWC
pnpm
- Framework
NestJS 12
OpenAPI 3.1
- Data
Prisma 6
MongoDB (replica set)
Redis
- Validation
Zod 4
- Jobs
BullMQ
- Auth and access
JWT (ES256, ES512)
Google sign-in
Apple sign-in
- OTP
TOTP
CASL 7
- API keys
- Cloud
AWS S3
AWS SES
Firebase Cloud Messaging
HashiCorp Vault (optional)
- Ops
- Helmet
sliding-window rate limiting (Redis)
Sentry
Pino
- health checks
i18n
- Testing
Vitest 5
- Delivery
Docker
GitHub Actions (EC2, ECS, SSH)
09Documentation
This page is the engineering story: what the boilerplate is for, how a request moves through it, and why it is built this way. It is not the manual.
How to install, configure and use it lives in the repository, in 32 guides under docs/. Start at docs/readme.md, which lists them in order from installation to release.