Every time I started a new NestJS project, I spent the first 2-3 days doing the same thing: setting up authentication, configuring the database, implementing caching, writing guards, and organizing the project structure. By the third project, I was tired of reinventing the wheel.
So I decided to build a boilerplate that would handle all the repetitive stuff — but this time, I wanted to do it right.
The Problem: NestJS is powerful, but setup is painful
Don't get me wrong — NestJS is an incredible framework. But when you're building production apps, there's a huge gap between the basic CLI template and what you actually need:
- Authentication & Authorization: Not just "login works" but sessions, OTP, role-based permissions
- Database layer: Migrations, transactions, proper error handling
- Caching strategy: Redis integration that actually makes sense
- Testing setup: Unit, integration, and E2E tests that don't fight with your DI container
- Architecture: A scalable structure that doesn't turn into spaghetti after 3 months
I tried several approaches over my last few projects:
Approach 1: Copy-paste from previous projects
Problem: Every project had slightly different requirements. Copy-pasting meant bringing old bugs and outdated dependencies forward.
Approach 2: Use existing boilerplates from GitHub
Problem: Most were either too minimal (just auth + db) or too opinionated (locked into specific libraries I didn't need). Many were abandoned after 1-2 years.
Approach 3: "This time I'll document everything properly"
Problem: Documentation gets outdated fast. Without automation, manual setup still took forever.
The Solution: CQRS + Event-Driven Architecture
After building my 4th project, I finally sat down and thought: What if I automated the entire setup AND used an architecture that scales?
I chose CQRS (Command Query Responsibility Segregation) because:
- Separation of concerns: Write operations (Commands) stay separate from read operations (Queries)
- Easier testing: You can test business logic without touching the database
- Event-driven side effects: Send emails, clear cache, update analytics — all decoupled from your main business logic
- Scales naturally: When you need to optimize reads vs writes, the separation is already there
Here's what the structure looks like in practice:
// CreateProductHandler — handles the write operation
import { Logger } from '@nestjs/common';
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
import { DatabaseService } from '../../../database/database.service';
import { ProductResponseDto } from '../dto/product-response.dto';
import { ProductCreatedEvent } from '../events/product-created.event';
import { CreateProductCommand } from './create-product.command';
@CommandHandler(CreateProductCommand)
export class CreateProductHandler implements ICommandHandler<CreateProductCommand> {
private readonly logger = new Logger(CreateProductHandler.name);
constructor(
private readonly database: DatabaseService,
private readonly eventBus: EventBus,
) {}
async execute(command: CreateProductCommand): Promise<ProductResponseDto> {
this.logger.log(`Creating product: ${command.data.name}`);
const product = await this.database.product.create({
data: {
name: command.data.name,
description: command.data.description,
price: command.data.price,
stock: command.data.stock ?? 0,
isActive: command.data.isActive ?? true,
},
});
this.logger.log(`Product created with ID: ${product.id}`);
// Emit event — other parts of the system react independently
this.eventBus.publish(new ProductCreatedEvent(product.id, product.name));
return new ProductResponseDto({
...product,
price: Number(product.price),
});
}
}
With CQRS:
- Your controllers are thin (just dispatch commands/queries)
- Business logic is isolated and testable
- Side effects don't block your main flow
- You can add new features without touching existing code
The Result: From 3 days to 5 minutes
After implementing everything, I packaged it into a boilerplate with a single bootstrap command:
pnpm bootstrap
This command:
- ✅ Installs dependencies and git hooks
- ✅ Spins up PostgreSQL and Redis via Docker
- ✅ Runs database migrations
- ✅ Seeds initial data
- ✅ Leaves everything ready — just run
pnpm devto start
Before: 2-3 days of setup per project
After: 5 minutes
What I learned building this
1. CQRS isn't overkill — it's protection
At first, CQRS felt like over-engineering. But after the project grew past 10 modules, I realized: the separation between commands and queries kept the codebase organized. New developers onboarded faster because the patterns were consistent.
2. Event-driven architecture saves you from "just one more line"
Every time you think "I'll just add this one side effect here," you're creating coupling. Event handlers keep your code clean and make refactoring painless.
3. Automation compounds
Every minute spent automating setup saves hours across multiple projects. The bootstrap script alone has saved me 20+ hours this year.
4. Better Auth > Passport.js
I switched from Passport to Better Auth and never looked back. Type-safe sessions, OTP, and RBAC in a modern API. If you're still using Passport in 2026, give Better Auth a try.
5. Tests need real dependencies sometimes
I used to mock everything in tests. Now I spin up Postgres via Docker for integration tests. Slower? Yes. But the confidence level is 10x higher.
The boilerplate ships with 327 tests across unit, integration, and E2E — 99%+ coverage across statements, branches, and lines.
Try it yourself
If you're tired of rewriting the same NestJS setup code, a demo version of the boilerplate is available:
🔗 Demo Repository — See the full CQRS architecture in practice
The demo is a read-only structural preview — not a runnable project. Use it to inspect the folder structure, read the full README, and see real examples of CQRS handlers, event handlers, and tests. Running pnpm lint or pnpm typecheck will throw errors because several modules are intentionally omitted.
The demo includes:
- Complete folder structure with all modules visible
- Real CQRS examples (commands, queries, event handlers)
- Example tests (unit + integration)
- Prisma schema with full data model
- Full README with architecture documentation
📦 Full Version — The complete, production-ready codebase
The full version adds:
- Complete authentication system (Better Auth — sessions, OTP, password reset)
- Role-based access control (RBAC) with full permissions
- Redis caching with event-driven invalidation and
@Cache()decorator - Rate limiting pre-configured
- Type-safe i18n translation system
- Full test suite — 327 tests (unit + integration + E2E), 99%+ coverage
- API documentation with Swagger
- Git hooks with Lefthook and Commitlint
- Biome for linting and formatting (100x faster than ESLint)
- Fail-fast config validation (12-Factor App)
- 3,600+ lines of technical documentation
One-time payment. Every time I improve the stack, you get the latest version at no extra cost.
What's your experience with NestJS project setup? Have you found a better way to handle the initial boilerplate? Let me know in the comments!
A backend dev building to save other devs' time. More on GitHub and ch1efthedev.gumroad.com.



Top comments (0)