All articles
Product#architecture#microservices#monolith#startups

Monolith vs microservices for startups: what to choose and when to split

Why most startups should start with a well-structured modular monolith, the real signals that justify extracting services, and how to split without a rewrite.

MF

Muhammad Faizan

Founder & Lead Engineer

5 min read

Few architecture debates generate as much heat as monolith versus microservices. For a startup, though, the decision is usually more straightforward than the debate suggests. The real question is not which architecture is better in general, but which one lets a small team ship and change the product fastest today, without making tomorrow harder.

Our default advice for most early products is a modular monolith: one deployable application with strict internal boundaries. This article explains why, what signals mean it is time to split, and how to do it gradually.

What you are really choosing between

A monolith is one application, deployed as a unit, usually with one database. Microservices are several independently deployable applications, each owning its own data and communicating over the network. The difference that matters is not code size; it is deployment and data independence.

That independence has real benefits for large organisations: teams can release without coordinating, and a failure in one service can be contained. It also has real costs: network calls that can fail, data spread across databases, distributed tracing, versioned APIs between services, and far more infrastructure to run and monitor.

Notice that neither option says anything about code quality. A monolith can be cleanly modular or a tangled mess, and microservices can be well bounded or a set of tightly coupled pieces that happen to run in separate containers. Architecture style does not rescue unclear design; it changes where the costs of that design show up.

Why a monolith usually wins early

  • Your domain is still moving. Early on, you do not know where the right boundaries are. Moving code between modules is a refactor; moving it between services is a migration.
  • Transactions are simple. One database means one transaction can update an order and its payment together. Across services you need sagas, retries and compensating actions.
  • Operations are cheaper. One deploy pipeline, one set of logs, one thing to monitor and secure.
  • Debugging is easier. A stack trace shows the whole request instead of a chain of network hops.
  • Small teams do not block each other much. The coordination problem microservices solve mostly appears when many teams share one codebase.

Scale is the argument most often made for starting with microservices, and it is usually premature. A single application behind a load balancer, with a managed database, background workers and sensible caching, can carry a product a long way. When it stops being enough, the bottleneck is often one specific component, which is precisely the case for extracting that component rather than redesigning everything.

Build the monolith so it can be split

A monolith becomes painful when everything can reach into everything else. Prevent that from day one by organising code into modules by business domain, each with a small public interface, and forbidding imports of another module's internals.

src/modules/billing/index.ts
// Each module exposes a small public API. Other modules import only this file.
export { createInvoice, getInvoice } from "./service";
export type { Invoice } from "./types";

// Elsewhere, in the orders module:
// import { createInvoice } from "@/modules/billing";        // allowed
// import { invoiceRepo } from "@/modules/billing/repo";     // blocked by lint rule

A few rules make later extraction far easier:

  • Each module owns its tables. Other modules read that data through the module's interface, not with direct queries.
  • Use domain events inside the monolith, such as order placed, for side effects that do not need to happen in the same transaction.
  • Enforce boundaries with lint rules or architecture tests so they survive deadlines.
  • Keep background jobs on a queue, even if the worker runs in the same codebase.

None of this is expensive. It is mostly discipline: a folder structure, a lint rule and a habit in code review. The payoff is that when one module eventually needs to become a service, the boundary is already there and the data already has a single owner.

Signals that it is time to split

Extract a service when you can name the specific pain it removes. Growth alone is not a reason. These are the signals we look for:

  • Deploy contention. Several teams regularly wait on each other to release, and the cost is visible in delivery speed.
  • Very different scaling profiles. One component, such as video processing or an AI inference pipeline, needs very different resources from the rest of the app.
  • Different reliability or security needs. Payment handling or a public API may need stricter isolation, access controls or uptime than an internal admin tool.
  • Different technology needs. A machine learning component that is far better served by Python while the rest of the backend is TypeScript.
  • A stable boundary. The module's interface has not changed much in months, so splitting it will not create constant cross-service changes.

It is worth being honest about the opposite case too. If your main problem is that the codebase is hard to understand, slow to test or full of hidden coupling, splitting it into services will usually make that worse, not better. Fix the boundaries inside the monolith first; extraction then becomes a much smaller step.

How to split without a rewrite

  1. Pick one module with a clear, stable interface and a concrete reason to extract it.
  2. Make its data private first, removing any direct queries from other modules while still inside the monolith.
  3. Replace in-process calls with an interface that could be backed by HTTP or a queue, and switch side effects to events.
  4. Deploy the module as its own service behind that interface, with its own database or schema, and route traffic gradually.
  5. Add observability before cutting over: tracing across the boundary, error rates and latency on the new calls.
  6. Remove the old code path once the service has run reliably in production.

This is sometimes called the strangler fig approach: the new service grows around the old code until the old code can be removed. It is slower than a big-bang rewrite on paper, and much faster in practice because the product keeps working throughout.

Key takeaways

  • The real difference is independent deployment and data ownership, which carry real operational cost.
  • Most startups should start with a modular monolith organised by business domain.
  • Enforce module boundaries and data ownership from day one so a later split is a refactor, not a rewrite.
  • Split only when you can name the pain: deploy contention, scaling, isolation or technology needs.
  • Extract one service at a time, gradually, with observability in place before cut-over.

We design and evolve backends that start simple and split cleanly when needed. See our backend and API work, or talk to us about your architecture.

Frequently asked questions

Do microservices scale better than a monolith?

Not automatically. A monolith can be scaled horizontally behind a load balancer. Microservices help when different parts need very different scaling or when many teams need to deploy independently.

What is a modular monolith?

A single deployable application whose code is split into modules with clear boundaries and explicit interfaces, so that each module could later become a separate service.

When should a startup move to microservices?

When there is a concrete pain the split solves, such as teams blocking each other on deploys or one component with very different scaling or reliability needs, not because the product is growing in general.

Found this useful? Share it with your team.

LinkedInX

Need help with backend & apis?

Secure, scalable backends and APIs with Node.js, Nest.js and Python, built for real traffic.

Learn more