Skip to main content
Architecture8 min readDecember 11, 2025

Modular Monolith: The Architecture Nobody Talks About

Microservices get the conference talks. Monoliths get the criticism. The modular monolith quietly solves most of the problems both create.

James Ross Jr.
James Ross Jr.

Strategic Systems Architect & Enterprise Software Developer

The False Binary

The architecture conversation in software development is dominated by a false binary: monolith or microservices. Monoliths are portrayed as the legacy approach — everything tangled together, impossible to scale, destined for a painful rewrite. Microservices are portrayed as the modern approach — independently deployable, infinitely scalable, the architecture of successful companies.

The reality is messier. Most teams that adopt microservices early end up with a distributed monolith — services that cannot be deployed independently because they share databases, have synchronous call chains, and require coordinated releases. All the operational complexity of distribution with none of the organizational benefits.

The modular monolith sits between these extremes and is the right choice far more often than it gets credit for. It is a single deployable unit — one process, one database — but internally organized into well-defined modules with explicit boundaries, clear interfaces, and enforced separation. Each module owns its domain logic, its data, and its public API. Modules communicate through defined interfaces, not by reaching into each other's internals.


What Makes a Monolith "Modular"

A modular monolith is not just a monolith with folders. The difference is enforcement of boundaries.

Each module has a public interface. Other modules can only interact with it through this interface — typically a set of exported functions or a service class. The module's internal classes, database queries, and implementation details are not accessible to other modules. In TypeScript, this means careful use of barrel exports (index.ts files) that expose only the public API and keeping everything else module-private.

Each module owns its data. Even though all modules share a database, each module has its own set of tables (or schema) that only it queries directly. If the orders module needs customer data, it calls the customers module's public interface — it does not run a SQL query against the customers table. This establishes the data ownership pattern that would make extracting a module into a separate service straightforward later.

Module boundaries are enforced. Convention-based boundaries ("please don't import from the internals directory") erode over time. Effective modular monoliths enforce boundaries through build tooling, linting rules, or architectural fitness functions that fail the build when a module violates another module's boundary. Without enforcement, a modular monolith decays into a regular monolith within a year.

Modules communicate through defined patterns. Direct function calls for synchronous operations. An internal event bus for asynchronous operations where the caller does not need a response. These communication patterns mirror what would happen across service boundaries, making future extraction possible without redesigning the interaction model.


Why This Works for Most Teams

The modular monolith delivers many of the benefits attributed to microservices while avoiding their costs.

Independent development. Teams can work on different modules without stepping on each other, as long as they respect the module interfaces. This is the organizational benefit that actually matters — not independent deployment, but independent development. Most teams deploy on a shared schedule anyway.

Simple operations. One deployment artifact. One database to back up, monitor, and maintain. One log stream to search. No service mesh, no distributed tracing, no inter-service authentication. For teams without dedicated platform engineering, this reduction in operational burden is significant.

Strong consistency. Because all modules share a database, cross-module operations can use database transactions. Creating an order and decrementing inventory can be atomic. No sagas, no eventual consistency, no compensating transactions. This is a genuine advantage for domains where consistency matters more than independence.

Easy refactoring. Renaming a function that is used across module boundaries is a single refactor operation with the compiler verifying correctness. In a microservices architecture, the same rename requires coordinated changes across multiple repositories, API versioning, and careful deployment sequencing.

Extraction path. A well-structured modular monolith can be partially extracted into services later if organizational or scaling needs demand it. The module boundaries become service boundaries. The internal event bus becomes an external message broker. The public interface becomes an API. The extraction is incremental and driven by actual need rather than speculative architecture.


When to Choose Something Else

The modular monolith has genuine limitations. Being honest about them prevents the same architectural overreach that microservices suffer from.

Scaling individual modules independently is hard. If one module needs 10x more compute than the others, you scale the entire monolith to meet that one module's needs. If this happens frequently — if your workloads have genuinely different scaling profiles — extracting the resource-intensive module into a separate service makes sense. But most applications do not have this problem.

Polyglot technology is off the table. All modules share the same runtime, language, and framework. If one team wants to use Python for machine learning and another wants TypeScript for the API, a monolith cannot accommodate both. Microservices genuinely solve this.

Very large teams hit coordination limits. When you have 50 developers working on a single codebase, even with clean module boundaries, build times, merge conflicts, and release coordination become real friction. Organizations at this scale usually benefit from service extraction — not because microservices are architecturally superior, but because they reduce coordination costs across large teams.

For the majority of projects — startups, small-to-medium teams, internal business applications, SaaS products before product-market fit — the modular monolith is the architecture that delivers the most value with the least complexity. It is not a stepping stone to microservices. It is a legitimate destination that many systems should stay at permanently.


If you are starting a new project or evaluating your current architecture and want guidance on structuring a system that fits your team and scale, let's talk.


Keep Reading