Skip to content
HANA PlatformHANARAD
Engineering

Modular monolith vs microservices: what growing businesses need

Why most growing businesses are better served by a well-structured modular monolith than by microservices, and the specific signals that justify splitting services later.

Author
HANARAD Engineering Team
Published
Updated
Updated
Reading time
6 min read

Few architecture choices generate as much debate as modular monolith vs microservices. Microservices are often presented as the modern, scalable option and monoliths as legacy. For most growing businesses, that framing is backwards. A well-structured modular monolith delivers faster, costs less to run and is easier to change, while still scaling to serious traffic. Microservices solve real problems, but mainly problems that appear at a size and organizational complexity most companies have not yet reached.

This article explains both approaches in business terms, why we build a modular monolith by default, and the specific signals that indicate when part of a system should become a separate service.

What a microservices architecture is

In a microservices architecture, an application is split into many small services, each deployed independently, usually with its own database, communicating over the network through APIs or message queues. Orders, payments, inventory, notifications and users might each be a separate service, owned by a separate team, released on its own schedule.

  • Independent deployment: a team can release its service without coordinating with others.
  • Independent scaling: a busy service can be scaled without scaling everything else.
  • Fault isolation: a failure in one service need not take down the whole system, if designed carefully.
  • Technology freedom: each service can, in principle, use different languages or databases.

The hidden cost of microservices

Each of those benefits comes with a cost that is easy to underestimate. Calls that used to be simple function calls become network requests that can be slow, fail or time out. Data that used to live in one transaction is now spread across services, so keeping it consistent requires patterns such as sagas, outbox tables and compensating actions. Debugging a single user request may mean tracing it through several services and log streams.

  • More infrastructure: service discovery, API gateways, message brokers, container orchestration and distributed tracing.
  • More operational work: every service needs its own pipeline, monitoring, alerting and on-call ownership.
  • Harder data consistency: cross-service transactions are complex and error-prone.
  • Slower feature delivery when a change touches several services and their contracts.
  • Higher hosting costs from running many separate processes and supporting components.

What a modular monolith is

A modular monolith is one deployable application, internally divided into well-defined feature modules. Each module, such as orders, billing, inventory or users, owns its own logic and data, and exposes a clear public interface. Other modules use that interface rather than reaching into its internals or querying its tables directly. The result keeps the simplicity of a single deployment with much of the organizational clarity of separate services.

In our backend stack, this means one NestJS application split into feature modules, with controllers calling services and services calling repositories, never the other way round. Dependencies point inward, and every module can be tested on its own. You can see the full set of tools on our web and backend stack page.

Why it is not the old monolith problem

The reputation of monoliths comes from systems where everything depends on everything, so a small change in one area breaks something unrelated. That is a problem of structure, not of deployment. A modular monolith addresses it directly with enforced boundaries, consistent layering and tests per module. A badly structured microservices system, by contrast, can be just as tangled, only now the tangles cross the network.

Rules that keep module boundaries honest

Boundaries only help if they are respected every day, including under deadline pressure. We rely on a few simple conventions that reviewers check on every pull request, so the structure does not erode as the codebase grows.

  • Each module exposes a small public service interface, and everything else inside it stays private.
  • A module reads and writes only its own tables; it asks other modules for their data through their interfaces.
  • Shared request and response types live in one shared package rather than being copied between modules.
  • Controllers stay thin and never touch the database directly, so business rules sit in one predictable place.
  • Circular dependencies between modules are treated as a design problem to fix, not a warning to ignore.

Modular monolith vs microservices: a comparison

How the two architectures compare for a growing business
FactorModular monolithMicroservices
DeploymentOne application, simple pipelineMany services, many pipelines
TransactionsNative database transactionsDistributed patterns such as sagas
Feature speed for small teamsFastSlower due to coordination
ScalingHorizontal scaling of identical stateless instancesPer-service scaling
Operational overheadLow to moderateHigh
DebuggingOne codebase, one log stream per requestDistributed tracing required
Best fitMost products with one to a few teamsLarge organizations with many independent teams

How a modular monolith scales

A common misconception is that a monolith can only scale by buying a bigger server. That is true only if the application keeps state in memory. If services are stateless, with sessions, caches and job queues held in PostgreSQL and Redis, you can run many identical instances behind a load balancer and add more as traffic grows.

  1. Keep every instance stateless, so any instance can serve any request.
  2. Store sessions and shared state in PostgreSQL or Redis rather than in process memory.
  3. Move long-running work such as reports, imports and emails to background job queues.
  4. Paginate every list and index frequently queried columns so the database stays fast.
  5. Add caching only with a clear reason, a time-to-live and an invalidation plan.

These practices carry most business applications a very long way. Our cloud hosting and DevOps work, delivered with our group company Raidlayer, sets up exactly this kind of horizontally scalable deployment.

Signals that justify splitting out a service

Starting with a modular monolith does not rule out microservices. It makes them a later, deliberate decision based on evidence. Because modules already have clean boundaries, extracting one into its own service is a contained project rather than a rewrite.

  • One module has load characteristics very different from the rest, such as heavy media processing or AI inference.
  • A module needs a release cadence or reliability target that conflicts with the main application.
  • Several teams are regularly blocked by sharing one deployment pipeline.
  • A component needs a different runtime, for example a Python service for machine learning models.
  • Regulatory or security requirements demand that some data or processing is isolated.

Even then, extract one module at a time and measure the result. Many systems end up with a modular monolith at the core and a small number of specialized services around it, which is a healthy and pragmatic outcome.

What this means for business owners

If you are commissioning a new system, be cautious of proposals that start with a large number of microservices for a first release. Ask what problem each service boundary solves today, and what it will cost to operate. A partner who recommends a simpler architecture is not cutting corners; they are protecting your budget and your speed to market.

Start simple, structure it well, and split only when the evidence says you should.

This principle applies across domains. An ERP system with inventory, purchasing, finance and HR modules is a natural fit for a modular monolith: the modules are distinct, but they share data constantly and benefit from single-database transactions.

Our default, and why

Every custom software development project we deliver starts as a modular monolith with stateless services, a layered structure and clear module boundaries. Microservices require explicit approval from our technical leadership, based on a documented need. That rule keeps our systems affordable to run, quick to change and easy for any developer in our pool to maintain.

If you are planning a new platform or wondering whether an existing system needs re-architecting, we are happy to review your situation and give you an honest view, including when the answer is to leave a working system alone.

About the author

HANARAD Engineering Team

Engineering & AI practice, HANARAD PLATFORM PRIVATE LIMITED

We are a team of 100+ developers based in Ahmedabad, India, all trained on one standardized web, mobile and AI stack. We write about the trade-offs we see while building and maintaining software for clients.

Published by HANARAD PLATFORM PRIVATE LIMITED · CIN U46512GJ2024PTC157221

FAQ

Questions readers ask

Let's build your next product

Book a free 30-minute consultation. We'll map your goals, suggest the right approach and outline a realistic plan — no obligation.