Skip to content

Monolith vs Microservices for Scalable Web Apps

Web Development6 min read

Should your web app be a monolith or microservices? Compare costs, scaling, team fit, and a path that avoids a costly rewrite.

In this article
  1. What a monolith really is
  2. What microservices really are
  3. Comparison
  4. Signs a monolith is still the right choice
  5. Signs it may be time to split
  6. Principles that matter in either style
  7. A simple modular structure
  8. A path that avoids rewrites
  9. Common mistakes
  10. Planning an architecture with us

For most new products, start with a well-structured monolith and split out services only when a specific, demonstrated problem justifies the extra complexity. A monolith is a single deployable application; microservices split the system into many small, independently deployed services that communicate over a network. Microservices can help large teams ship independently and scale hot spots separately, but they add operational cost from day one. A modular monolith gives you most of the structure with far less overhead.

Scalability is often discussed as if it were only about traffic. In practice it covers three things: handling more users, handling more features without the code becoming unmanageable, and allowing more developers to work without blocking each other. The architecture you choose should address the constraint you actually have, not one you might have in three years.

What a monolith really is

A monolith runs as one process (or several identical copies behind a load balancer). All features share one codebase and, typically, one database. It is easy to run locally, easy to test end to end, and a single deployment updates everything.

"Monolith" is not a synonym for "messy". A modular monolith divides the code into clearly bounded modules, such as billing, accounts, orders and notifications. Each module has a defined public interface, owns its own tables, and does not reach into the internals of another. This discipline is what makes later extraction possible.

What microservices really are

In a microservice architecture each service owns a business capability and its data, is deployed on its own, and communicates through APIs or messages. Teams can choose different technologies per service, scale services independently and release them on separate schedules.

The cost is that you have turned in-process function calls into network calls. Network calls fail, time out and arrive out of order. You now need service discovery, distributed tracing, centralised logging, API versioning between services, and a way to handle transactions that span several databases. Much of this work belongs to the cloud and DevOps side of a project, and it must be budgeted for.

Comparison

ConcernModular monolithMicroservices
Initial build speedFaster; one codebase, one pipelineSlower; infrastructure and contracts first
Local developmentSimpleNeeds several services running or mocked
DeploymentOne unitMany units, each with its own pipeline
Data consistencyNormal database transactionsEventual consistency, sagas, compensation logic
ScalingScale the whole app horizontallyScale only the busy services
Failure isolationA bug can affect everythingBetter isolation if designed well
Team autonomyShared code needs coordinationTeams own services end to end
Operational loadLowerHigher: monitoring, tracing, orchestration

Signs a monolith is still the right choice

  • One small team (roughly one team that can sit around a table) builds and owns the product.
  • You are still discovering what customers want, so boundaries between features keep moving.
  • A single relational database satisfies your reporting and consistency needs.
  • You lack the monitoring and deployment automation that many services require.

When requirements are unsettled, wrong service boundaries are expensive. Moving code between modules in a monolith is a refactor; moving it between services is a migration project.

Signs it may be time to split

  • Independent scaling needs. One component, such as image processing or report generation, needs very different resources from the rest.
  • Team contention. Several teams keep blocking each other in one codebase and one release train.
  • Different reliability requirements. A payments component must stay up even if a recommendation feature fails.
  • Technology constraints. A capability genuinely needs a different runtime or language.
  • Long deploy and test cycles that slow everyone, which better module boundaries and test strategy cannot fix.

Notice that each of these is a measurable problem you can point to. "Microservices are modern" is not one of them.

Principles that matter in either style

Keep state out of the application servers

If sessions or uploaded files live on one server's disk, you cannot add a second server safely. Store sessions in a shared store such as Redis, and files in object storage. Then any instance can serve any request, which is the basis of horizontal scaling.

Design the database deliberately

The database is usually the first real bottleneck. Index the queries you actually run, avoid fetching more than you display, and use read replicas or caching only when measurements show a need. Many products run happily on a single well-tuned PostgreSQL instance for a long time.

Move slow work off the request path

Sending emails, generating PDFs or calling third-party APIs should not block the user's request. Put the work on a queue and process it in a background worker. This one habit removes many apparent scaling problems.

Use clear contracts between modules

Whether modules talk through function calls or services talk through HTTP, define the interface explicitly and test it. Our article on REST vs GraphQL covers choosing the style for the external API.

Observe before you optimise

Add logging, metrics and error tracking early. Scaling decisions made without data are guesses.

A simple modular structure

Here is a folder layout for a Node.js and TypeScript modular monolith. Each module exposes only what other modules need through its index.ts:

src/
  modules/
    accounts/
      index.ts        // public interface
      accounts.service.ts
      accounts.repo.ts
    billing/
      index.ts
      billing.service.ts
      billing.repo.ts
    orders/
      index.ts
      orders.service.ts
      orders.repo.ts
  shared/
    db.ts
    logger.ts
  server.ts

A rule such as "modules may import only from another module's index.ts" can be enforced with a lint rule. When one module later needs to become a service, its boundary already exists and the calls through its interface can become network requests.

A path that avoids rewrites

  1. Build a modular monolith with explicit boundaries and one database schema per module (or clearly separated tables).
  2. Keep sessions and files outside the application servers.
  3. Introduce a queue and background workers for slow tasks.
  4. Add monitoring, tracing and alerts, and watch real usage.
  5. If one module becomes a proven bottleneck, extract that module as a service, leaving the rest alone.
  6. Repeat only when data justifies it.

This sequence is sometimes called the strangler approach: new services grow around the old system gradually, without a risky big-bang rewrite.

Common mistakes

  • Starting with microservices for an unproven product. You pay the full operational cost before you have users to justify it.
  • The distributed monolith. Services that must be deployed together, or that share one database, give you the downsides of both styles.
  • Splitting by technical layer (a "database service", a "validation service") instead of by business capability.
  • Chatty services. If one user action triggers a dozen calls between services, latency and failure rates multiply.
  • No ownership. A service without a named owning team decays quickly.
  • Scaling before measuring. Adding caches, replicas and services without profiling often moves the bottleneck without removing it.
  • Forgetting the front end. A fast back end is wasted if pages are slow; see how rendering choices affect this in our rendering guide.

Planning an architecture with us

The right architecture depends on your team size, your growth expectations, your compliance needs and your budget for operations. We would rather help you start simple and evolve with evidence than sell you complexity. If you are planning a new platform or untangling an existing one, our web application development team covers API development, multi-tenant platforms and microservices architecture, and can advise on which fits your situation.

Share this article

General educational information, not legal or professional advice for your specific situation.

Frequently asked questions

Should a startup begin with microservices?

Usually not. Microservices add operational overhead before you know where your boundaries should be. A modular monolith is quicker to build and can be split later if data shows a need.

What is a modular monolith?

A single deployable application whose code is divided into well-defined modules with clear interfaces, each owning its own data. It keeps deployment simple while preserving future options.

When should I move to microservices?

When you can point to a concrete problem: a component needing independent scaling, teams blocking each other, different reliability needs, or a genuine technology constraint.

Do microservices make an app faster?

Not by themselves. They can help you scale busy parts independently, but network calls add latency. Speed usually depends more on database design, caching and efficient code.

Keep reading

Let’s build it

Ready to build with CloudWiss?

Tell us what you want to build, move to the cloud or keep running. We reply with a clear next step.