Scalable NestJS architecture — from module to monorepo
Our large NestJS projects start as "one app with modules". Today anything with more than ten modules lives inside a monorepo. This note explains when we make each leap and what the team gains from it.Stage 1: Single app …

Our large NestJS projects start as "one app with modules". Today anything with more than ten modules lives inside a monorepo. This note explains when we make each leap and what the team gains from it.
Stage 1: Single app + modular modules
- Every feature lives in its own module (Auth, Billing, Content…).
- Shared types/DTOs only inside
src/common. - e2e tests live inside each module to keep it isolated.
Stage 2: Monorepo + libraries
When the system grows we use nest g app|lib. Libraries become shared contracts (types / DTOs / interfaces); apps stay independently deployable.
Patterns we rely on
- Lean controllers: accept a DTO, call a use-case / service.
- Service = application layer — orchestrates repositories and domain.
- Repository: interface + TypeORM/Prisma implementation; swappable with a fake.
- Domain: pure value objects and business rules.
Boundary rule: the domain does not know about the DB; the infrastructure does not contain business logic. That is the real difference between DDD and MVC.
Observability
- Pino for structured logs.
- OpenTelemetry with Tempo/Grafana for distributed tracing.
- Prometheus + custom exporters for business metrics.
Takeaway
Scalable architecture is not a framework — it is clear boundaries. Drawing them on day one saves an 18-month rewrite.


