Self-hosted service
andrechristikan/ack-nestjs-boilerplate avatar
andrechristikan/ack-nestjs-boilerplate

ack-nestjs-boilerplate: a NestJS 12 backend starter with tenancy and TOTP already wired

NestJS 12 boilerplate with JWT, OAuth (Google & Apple), TOTP/2FA, RBAC, and multi-workspace tenancy. Prisma on MongoDB, modular repository layer, native ESM. Production-ready.

701 stars235 forksTypeScriptMIT

At a glance

What is it?
ACK NestJs Boilerplate is a NestJS v12 starter that ships JWT with stateful sessions, Google and Apple sign-in, TOTP two factor, CASL policies, workspace scoped projects and Redis backed queues, with Prisma on MongoDB and native ESM. It suits a team that wants an authenticated multi tenant backend on day one, and it does not suit a team that wants a relational database or a small service with a few endpoints.
Who is it for?
Adopt ack-nestjs-boilerplate if you are building a multi tenant NestJS service and want authentication, workspaces, feature flags, notifications and file upload already in place rather than in your backlog. Do not adopt it if your data belongs in PostgreSQL, since the move to PostgreSQL is still an open item and Prisma transactions on MongoDB force a replica set into your local and production setup.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 7 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the boilerplate wires before you write a line of business code

ACK NestJs Boilerplate is described as a NestJs v12.x boilerplate with JWT, OAuth for Google and Apple, TOTP two factor and RBAC. It runs Prisma on MongoDB with a replica set required, uses native ESM, and keeps data access behind a repository layer. The stated objective is a modular, component based folder structure with stateful authentication and authorization, the Repository Design Pattern, Twelve-Factor App practices, native ESM, and multi workspace tenancy where projects stay scoped to a workspace.

The intended audience is spelled out in a short list. Enterprise applications that need roles, CASL policies and an activity log. Authentication services. Mobile backends with REST APIs, social login, device tracking and push notifications. Multi tenant SaaS, where every user belongs to a workspace and projects are workspace scoped with invites and join requests. And startup MVPs, where auth, workspaces, notifications and file upload are the parts that usually consume the first month.

That last group is the one to weigh, because the same list that makes this attractive for an MVP makes it heavy for anything small. The package is private in the manifest, which means it is not published to a registry for consumption as a dependency, and the repository is MIT licensed so the code can be used and reshaped freely.

The feature surface is wide. Authentication covers ES256 access tokens and ES512 refresh tokens, Redis backed sessions with instant revocation, TOTP with encrypted secrets and backup recovery codes, API keys through an x-api-key guard, and Redis sliding window rate limits per IP, per user and per route. Storage covers AWS S3 presigned uploads for public and private buckets, Redis caching with configurable TTLs, and both offset and cursor pagination.

MongoDB must be a replica set, and that shapes your local setup

The first thing the documentation flags under Important is that MongoDB must run as a replica set, because Prisma transactions need it. That is not a stylistic preference, it is a hard requirement of the data layer, and it propagates into the environment file. The connection string shipped in the example configuration carries the replica set name and the write concern settings:

bash
APP_ENV=local
HTTP_PORT=3000
DATABASE_URL=mongodb://localhost:27017/ACKNestJs?retryWrites=true&w=majority&replicaSet=rs0
CACHE_REDIS_URL=redis://localhost:6379/0
QUEUE_REDIS_URL=redis://localhost:6379/1
SENTRY_DSN=

Note that Redis appears twice, on database 0 for the shared cache and on database 1 for the queue, so the two workloads are separated by database index rather than by instance.

The compose file uses mongo:latest and redis:latest, and the version table pins MongoDB and Redis at v8 or newer. The build list also expects Node 24.15.0 or newer, pnpm 10.25.0 or newer with [email protected] pinned, TypeScript v6.0.x, Prisma v6.19.x, Docker v28.5.x or newer and Docker Compose v2.40.x or newer.

The open question in the project's own TODO list is a move to PostgreSQL, which is listed as not done yet, alongside a move to a monorepo that would split the backend application from a separate documentation website. Until that move happens, the relational option is yours to build, and the replica set stays in the design.

The protection decorator stack is order sensitive

This is the part of the project that will bite you first if you ignore it. The documentation is unusually direct: protection decorators follow a fixed stack, you should use only the ones you need, and you should keep that relative order. The canonical example looks like this:

typescript
    @Doc({ summary: '…' })
    @Response('example.get')          // or @ResponsePagination / @ResponseFile
    @TermPolicyAcceptanceProtected(...)
    @PolicyProtected({...})
    @RoleProtected(...)
    @ProjectMemberProtected(...)      // never on /admin
    @ProjectProtected()               // never on /admin
    @WorkspaceMemberProtected(...)    // never on /admin
    @WorkspaceProtected()             // never on /admin
    @UserProtected()
    @FeatureFlagProtected(...)
    @AuthJwtAccessProtected()
    @ApiKeyProtected()
    @Get('/some-endpoint')

Nest runs the stack bottom-up, so a decorator that needs state from another one must sit above it. The documented example is the feature flag guard, which has to sit above the JWT access guard so the flag guard can see request.user. Flip that order and there is no user yet, targetUserIds is skipped, and the rollout keys off the anonymous-ID header instead. The failure is silent, which is the dangerous part: your percentage rollout starts serving the wrong population.

The comments about never putting the workspace and project guards on an admin route are equally practical. Activity logging is handled separately from this stack: domains stage events and a global interceptor writes them, so the log does not depend on where you place the protection decorators.

Workspaces, projects and percentage feature flags

Tenancy is the organising idea. Every user gets a workspace, personal by default, or the inviting workspace when they arrive through an invite. Projects live inside workspaces, with membership, invites and join requests, and a feature flag can turn that project surface off when you do not want it exposed yet.

On top of that sits the feature flag system, which supports on and off states, target users, and percentage rollout keyed either by user or by anonymous ID. The anonymous ID path is the one the decorator ordering note above refers to, and it exists so that a rollout can work for visitors who have not signed in yet.

The membership model then feeds the authorization model. RBAC and CASL policies sit on top of workspace and project membership, with roles and fine-grained abilities. The same idea appears in the workspace and project guards, which decide whether the caller is a member and whether the route is a protected workspace or project surface.

This is the part of the boilerplate that is hardest to remove later. The tenancy model is not a single service you can delete; it is woven into the guards, the data access layer and the way every user is created, so changing the ownership model after launch means touching most of the codebase.

The compose stack runs the app, Mongo, Redis and a JWKS server

The local stack is described by a compose file named ack-nestjs-boilerplate, and the API service is more than a plain container. The apis service builds from ci/dockerfile.local, publishes port 3000 on the host, mounts the src, prisma and .env directories into the container for live editing, and restarts on failure. It also declares a healthcheck that wgets the path /api/public/hello every 30 seconds with 10 second timeouts and 3 retries, plus resource limits of 1 CPU and 2G of memory.

yaml
name: ack-nestjs-boilerplate

services:
    apis:
        build:
            context: .
            dockerfile: ci/dockerfile.local
        hostname: apis
        ports:
            - 3000:3000

The most interesting dependency is the jwks-server, an nginx:latest container that publishes port 3011 and serves two key files read only from the keys directory, access-jwks.json and refresh-jwks.json, under the .well-known path, with its own nginx configuration from ci/jwks-server/jwks.conf. That is the JSON Web Key Set endpoint the token verification points at, and the example environment points the access token JWKS URI at http://localhost:3011/.well-known/access-jwks.json and the refresh token URI at the matching refresh file.

The api service waits for redis, mongo and jwks-server to report healthy before it starts, which is the right way to sequence a replica set that has to initialise. The jwks-server itself is limited to 0.3 CPU and 128M, which is a sensible budget for a static file server and a hint about what this component really is.

Environment configuration is explicit and environment dependent

The .env.example file is long and structured by concern, which is good practice for a starter: application identity, HTTP host and port with a trusted proxy setting, CORS, URL versioning with an enabled flag and a version number, then the database, then the JWT block, then social sign-in credentials, then AWS S3 and SES, then Redis, then monitoring and push.

Two behaviours depend on the environment and are easy to miss. When APP_ENV is production, Swagger is turned off, and Sentry only receives the warn, error and fatal log levels, while other environments send every level. That is a sensible default, and it means a staging box will not warn you about the log volume it would send in production.

The example values also show the shape of the security configuration. The JWT issuer and audience are set explicitly, access tokens expire after 1h and refresh tokens after 30d, and there are separate KID, private key and public key settings for each token type, which is what lets the two token families have separate signing keys and separate JWKS documents. TOTP has its own issuer and encryption key, and the two factor secrets are stored encrypted.

Secrets are generated by the project rather than invented. The scripts include generate:secret, which runs a Node script for all secrets, and separate jwt and encryption variants, and the generate script chains the Prisma client generation with the package generation.

Toolchain, scripts and what a plain Nest application gives you instead

The scripts in the manifest explain how the project expects to be worked on. A preinstall hook runs npx only-allow pnpm, so an accidental npm install fails on purpose. The build runs nest build for the application and then a typecheck, and the start script launches node with the compiled instrument entry point. Formatting is prettier over src and test, dead code detection is knip, spell checking is cspell with its own config, and the test setup uses vitest with a vitest.config.ts at the root. Commit hygiene is handled by a commitlint config, husky and a .prettierrc.

bash
pnpm db:generate && pnpm generate:package

That command is the generate step, and it pairs the Prisma client generation with the package generation script, which exists because the project is laid out as a monorepo-style workspace with a pnpm-workspace.yaml. The Docker and CI pieces live in ci/, and the prisma/ directory holds the schema, which the compose file mounts into the container so schema changes do not require a rebuild.

The alternative to all of this is a plain Nest application. The difference in approach is not about code quality, it is about where the work sits. Starting from an empty Nest project you write your own repository layer, your own session handling, your own guards, and you run MongoDB or PostgreSQL the way you like, including skipping the replica set requirement. Starting from this boilerplate you inherit decisions that are already made and internally consistent, including the tenancy model, the JWKS server and the decorator ordering, and you pay for them with a codebase whose structure you did not choose and a database choice that is still flagged as pending change in the TODO list.

On maintenance, the repository is not archived and the last push was on 2026-10-16. The only listed release is v9.0.0 from 2026-09-21, and the MIT licence means the code can be adopted, modified and shipped without licence obligations beyond attribution.

Editorial conclusion

Adopt ack-nestjs-boilerplate if you are building a multi tenant NestJS service and want authentication, workspaces, feature flags, notifications and file upload already in place rather than in your backlog. Do not adopt it if your data belongs in PostgreSQL, since the move to PostgreSQL is still an open item and Prisma transactions on MongoDB force a replica set into your local and production setup. Verify first that your environment satisfies Node 24.15.0 or newer, pnpm 10.25.0 or newer with [email protected] pinned, and Docker Compose 2.40.x or newer, then confirm the MongoDB service starts as a replica set before writing any code.

Frequently asked questions

What does the ack-nestjs-boilerplate project include?

It is a NestJs v12.x boilerplate with JWT and stateful sessions, Google and Apple OAuth, TOTP 2FA, RBAC with CASL policies, multi workspace tenancy with projects, feature flags, Redis caching, BullMQ queues, AWS S3 uploads and Prisma on MongoDB, using native ESM.

Why does ack-nestjs-boilerplate require a MongoDB replica set?

Because Prisma transactions need it, and the example connection string points at replicaSet=rs0. The project lists a move to PostgreSQL as an open item in its TODO list, so the document database with a replica set is still the shipped design.

How do I install and run the ack-nestjs-boilerplate stack?

The compose file starts the api service, MongoDB, Redis and a jwks-server, and the service waits for redis, mongo and jwks-server to be healthy. The environment is copied from .env.example, and the client is generated with pnpm db:generate && pnpm generate:package, with the api published on port 3000 and the JWKS endpoint on 3011.

Why does the decorator order matter in ack-nestjs-boilerplate?

Nest runs the protection decorators bottom-up, so a guard that needs state from another must sit above it. The documented case is @FeatureFlagProtected() above @AuthJwtAccessProtected(), so the flag guard can see request.user; reversing them skips targetUserIds and rolls out on the anonymous-ID header instead.

What are the version requirements for ack-nestjs-boilerplate?

NestJs v12.x, NodeJs 24.15.0 or newer, pnpm 10.25.0 or newer with [email protected] pinned, TypeScript v6.0.x, Prisma v6.19.x, MongoDB v8 or newer, Redis v8 or newer, Docker v28.5.x or newer and Docker Compose v2.40.x or newer.

Official sources

  1. andrechristikan/ack-nestjs-boilerplate on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/andrechristikan-ack-nestjs-boilerplate.svg)](https://hysenlabs.com/projects/andrechristikan-ack-nestjs-boilerplate)

Was this useful?