All posts
5 min read

Monolith vs Microservices: Why Small Teams Should Start With a Monolith

Microservices solve organisational problems most small teams don't have and add distributed-systems problems they can't afford. What each costs, the modular monolith as a middle path, the signals that justify splitting a service out, and how AI coding agents change the maths.

architectureinfrastructureengineering managementintermediate

Ask an AI tool to "design a scalable architecture" and there's a fair chance you'll get microservices: an auth service, a user service, a notification service, a gateway, a message queue, and a Kubernetes manifest for each. It looks professional. For a team of one to ten people, it's usually a mistake.

Definitions

A monolith is one deployable application containing all of your backend's functionality. One codebase, one build, one process type (maybe plus workers), usually one database.

Microservices split functionality into separately deployed services that communicate over the network — HTTP, gRPC, or a message broker — each ideally owning its own data.

Both can be well or badly built. The question is which set of problems you'd rather have.

What microservices actually buy you

The real benefits are mostly organisational:

  • Independent deployment. Team A ships without coordinating with Team B.
  • Ownership boundaries. Each team owns a service end to end, with a clear contract.
  • Independent scaling of hot paths — the image resizer can run on 40 instances while the admin panel runs on one.
  • Technology freedom per service, and fault isolation between them (in theory).

If you have dozens of engineers stepping on each other in one codebase, these matter a lot. If you have three, they solve problems you don't have.

What microservices cost

Everything that was a function call becomes a network call, and the network is unreliable, slow, and asynchronous:

  • Partial failure. Service B is down or slow. Does A retry? Time out? Degrade? Every call site needs an answer.
  • No transactions across services. "Create the order and decrement stock" was one database transaction. Across two services it's a distributed consistency problem — sagas, outboxes, compensating actions. (The Transactional Outbox Pattern.)
  • Data joins become API calls. A report that was one SQL query becomes fan-out requests and in-memory joins.
  • Versioning contracts. Changing an API now requires coordinating deploys or supporting old and new shapes simultaneously.
  • Operational surface. N deploy pipelines, N sets of logs, service discovery, distributed tracing to follow one request, and N times the dependency updates.
  • Local development. Running the whole system on a laptop becomes its own project. (Docker Compose for Local Development.)

Most of the cost is paid up front and continuously; most of the benefit arrives only at a scale many products never reach.

The modular monolith

The pragmatic middle path: one deployable, with strong internal boundaries.

src/
  modules/
    billing/      # public interface: billing/index.ts
    catalog/
    identity/
    notifications/
  shared/

Rules that make it work:

  • Each module exposes a small public interface; other modules import only that, never its internals. Lint rules or tools like dependency-cruiser can enforce this.
  • Each module owns its tables. Others go through its interface rather than querying its data directly.
  • Cross-module side effects go through in-process events where it helps decoupling.

You get most of the clarity of service boundaries, keep transactions and simple debugging, and — if you ever need to — you have clean seams along which to extract a service later.

Signals that justify extracting a service

Split something out when a concrete pressure appears, not in anticipation:

  • A radically different resource profile — CPU-heavy video transcoding, GPU inference, or memory-hungry PDF rendering that would force you to scale the whole app for one feature.
  • A different reliability or security boundary — code running untrusted input, or a component with compliance requirements you want isolated.
  • Genuinely independent teams blocked on each other's release cadence.
  • A different runtime that's clearly the right tool (a Python ML component beside a Node app).

Even then, a separately deployed worker consuming a job queue from the same codebase covers many of these cases without a new service contract. (Your App Needs Background Jobs.)

How AI coding agents change the calculation

Two effects, pulling in the same direction:

  1. Agents work best with the whole system in view. In a monolith, a coding agent can trace a change from route to database, update every call site, run the full test suite, and verify the result in one environment. Across services, it sees one repository's slice of a contract and has to guess about the rest.
  2. Agents lower the cost of building but not of operating. Generating six services is now cheap. Debugging a request that fails intermittently across six services, at 2am, is exactly as hard as before.

An agent-friendly architecture is one where "run it and check" is easy — which favours a monolith with good tests and a reproducible environment. (Why AI Coding Agents Need Persistent Workspaces.)

Monorepo is a separate question

Don't conflate deployment architecture with repository layout. A monolith lives in one repo by definition; microservices can live in one repo or many. (Monorepo vs Polyrepo.)

A decision summary

Situation Recommendation
Solo builder or small team, new product Monolith, organised by module
One feature with a very different resource profile Monolith plus a separate worker or service for that feature
Several teams blocked on shared releases Extract services along the most painful boundary first
"We might need to scale someday" Monolith. Scale it vertically and horizontally first — a well-indexed monolith on one decent server goes a long way.

EasySpawn gives each project a persistent workspace where the whole monolith — app, workers, and managed Postgres — runs together, so Claude Code can change and verify the full system in one place. See how it works or join the waitlist.

Related: What Is a Tech Stack? · Monorepo vs Polyrepo · Designing a REST API

Keep reading