The worst codebase we ever inherited was a Node.js application for a property management company. 180,000 lines of TypeScript in a single src directory. No folders for domains, no service layer, no separation between HTTP handling and business logic. Route handlers directly queried the database, applied business rules, sent emails, and returned HTML. We restructured it into seven well-defined modules over four months without a single day of downtime.
The first pattern is Strangler Fig for modules. You do not refactor the entire codebase at once. You identify the next feature that needs building and build it in a new module with clean architecture. Old code stays in place. New code lives in the new structure. Over time, as features and fixes accumulate, more code moves into modules and less remains in the legacy tangle.
The second pattern is the Anti-Corruption Layer. When new module code needs to interact with legacy code, create an interface describing what you need and implement it as a thin wrapper around the legacy functions. This prevents legacy patterns from leaking into clean modules. When you eventually refactor the legacy code, the module never changes.
The third pattern is an in-process Event Bus. Modules communicate through typed events, not direct function calls. Payments publishes PaymentReceived. Notifications subscribes and sends emails. Accounting subscribes and updates the ledger. A simple typed EventEmitter is sufficient. No message broker required.
The fourth pattern is Database Schema as Module Boundary. Each module owns its database tables in a named PostgreSQL schema. Other modules access data only through the owning module's public API, enforced by import path lint rules.
The fifth pattern is Incremental Extraction. Prioritize modules by three criteria: upcoming feature work (immediate payoff), isolation from other domains (easiest to extract), and bug density (immediate quality improvement). Extract the highest-scoring domain area next.
The key metric we tracked was new code percentage. What percentage of weekly code went into clean modules versus legacy? Month 1: 40% new. Month 4: 90% new. The codebase improved every sprint without dedicated refactoring time.
The mistake we see most: attempting to extract all modules at once. Teams plan a big bang migration, spend two months refactoring without shipping features, lose business patience, and end up with a half-finished mess. Incremental extraction avoids this entirely because features keep shipping throughout.
About the Author
Fordel Studios
AI-native app development for startups and growing teams. 14+ years of experience shipping production software.
Event-driven architecture does not require Kafka, microservices, or a distributed systems background. Here is how we implement event-driven patterns in regular monolithic applications.
Command Query Responsibility Segregation sounds elegant in conference talks. In practice, it adds significant complexity. We share when we use it, when we avoid it, and a pragmatic implementation.

We will be the first to admit that hexagonal architecture is overengineered for most projects. But the alternative -- no architecture -- is worse. Here is our pragmatic middle ground.
We love talking shop. If this article resonated, let's connect.
Start a ConversationTell us about your project. We'll give you honest feedback on scope, timeline, and whether we're the right fit.
Start a Conversation