Large legacy monoliths are still very common in 2026 — especially in B2B SaaS, fintech, enterprise tools and marketplaces that started 5–10 years ago. They work, generate revenue, but become increasingly painful: slow releases, fear of touching code, growing technical debt, teams stepping on each other.
In the last 18 months we successfully migrated three large products (from 400k to 1.2M+ lines of TypeScript/Node.js) to a modular NestJS architecture. Here is the honest story — what we did, which patterns survived production, which we abandoned, and the measurable business outcomes.
Why We Decided to Migrate (Business Triggers)
The decision was never “NestJS is cool”. It was driven by concrete pain:
- Average time to release a feature → 3–6 weeks (instead of 3–7 days)
- One failed deployment could block the whole product for hours
- New developers needed 2–3 months to start contributing meaningfully
- Cost of infrastructure grew faster than traffic (because scaling was all-or-nothing)
- Business wanted to launch independent verticals (new pricing engine, separate white-label instances, embedded modules for partners)
We needed independent deployability, clear team ownership and predictable velocity — without a full rewrite.
Chosen Strategy: Strangler Fig + Vertical Slice + NestJS Modules
We deliberately did not do “big bang rewrite”. Instead we used a combination of three patterns:
- Strangler Fig — gradually replace pieces of the old system with new services
- Vertical Slice Architecture — group by business capability, not by technical layer
- NestJS as the module & DI backbone — strong structure, excellent TypeScript support, built-in support for microservices
How the migration actually looked in phases
Phase 0 – Preparation (1–2 months)
- Created shared library with auth, logging, config, validation, error handling
- Introduced strict ESLint + TypeScript rules across old and new code
- Set up monorepo (Turborepo + Nx) with caching and affected commands
Phase 1 – Extract Critical Read Paths (3–5 months) First we extracted high-traffic read-only endpoints (listings, dashboards, reports) into separate NestJS services. Why reads first? Lowest risk, instant performance wins (edge caching + Redis), quick feedback.
Phase 2 – Command Side + Event-Driven Core (6–12 months) Moved write operations and business logic. Key decision: domain events became the main communication protocol between modules (via RabbitMQ).
Phase 3 – Independent Deployability & Team Autonomy (ongoing) Each vertical (billing, users, content, analytics, etc.) became a separate NestJS application with its own repository, CI/CD, database schema subset and deployment pipeline.
Architectural Decisions That Survived Production
- NestJS Modules as the main boundary — each business domain = one NestJS module (later extracted to standalone app)
- Shared kernel — very thin (auth, config, logger, exceptions) — no business logic
- Anti-corruption layer between old monolith and new services
- RabbitMQ + domain events — for everything asynchronous and cross-module
- Redis — everywhere: caching, rate limiting, session store, temporary locks
- API Gateway (Kong / Traefik) + service mesh lite (linkerd) for routing and observability
- OpenTelemetry + Grafana Tempo — tracing became mandatory from day one
What We Tried and Abandoned
- Full micro-frontends → too heavy for our use-cases (stayed with module federation + Next.js)
- GraphQL Federation → complexity > value (stuck with REST + tRPC inside the ecosystem)
- Separate databases per service from the beginning → too painful for transactional consistency (we kept shared Postgres with schema-per-service + eventual consistency)
- Kafka → overkill for our scale (RabbitMQ handled everything perfectly)
Measurable Results After 12–18 Months
- Deployment frequency — from 1–2 per month → 15–40 per week (per vertical)
- Lead time for changes — from 18–42 days → 2–7 days
- Mean time to recover — from 4–12 hours → <45 minutes
- Velocity — teams deliver 2.5–4× more story points per sprint
- Infrastructure cost growth — almost flat despite 2.8× traffic increase
- Developer satisfaction (internal NPS) — from 4.1 → 8.7
Current Architecture Sketch (2026)
- Frontend → Next.js App Router + React Server Components
- API Layer → Multiple NestJS apps (one per major domain) + tRPC for internal calls
- Communication → RabbitMQ (events) + Redis (cache & sync)
- Database → PostgreSQL (main) + ClickHouse (analytics) + Redis
- Deployment → Kubernetes + ArgoCD + edge functions where possible
Bottom Line
Migrating a large monolith to modular NestJS architecture is not easy, but it is very much worth it when business growth is constrained by engineering velocity.
The biggest wins came not from NestJS itself (although its structure helped enormously), but from:
- clear ownership boundaries
- event-driven thinking
- ruthless focus on deployability
- incremental strangler approach
If your product is already generating revenue but engineering is becoming the bottleneck — start small, extract reads first, measure everything, and don’t try to be perfect from day one.


