5+ years software engineer
5+ years software engineer
5+ years software engineer
5+ years software engineer
Start with a well-structured (modular) monolith. Move to microservices only when your team size and independent scaling needs justify the very real operational complexity. That's the answer for the overwhelming majority of projects — and the mistake I see most often is teams adopting microservices for an app that a single monolith would serve better, cheaper, and with far less pain.
Let me back that up.
A monolith is one deployable application. All your features — auth, orders, payments, notifications — live in one codebase and ship as one unit.
Microservices split those features into independent services, each with its own codebase, deployment, and often its own database, communicating over the network (HTTP, gRPC, or a message queue).
The trade is fundamental: microservices buy you independence at the cost of operational complexity. Every benefit and drawback flows from that.
| Dimension | Monolith | Microservices |
|---|---|---|
| Architecture | One app, one codebase | Many independent services |
| Deployment | Deploy everything together | Deploy each service independently |
| Scaling | Scale the whole app | Scale individual services |
| Development | Simple; one repo, one run command | Complex; many repos, service coordination |
| Performance | Fast in-process calls | Network latency between services |
| Reliability | One failure can affect all | Isolated failures (if designed well) |
| Operations | One thing to monitor/deploy | Distributed systems, orchestration, observability |
| Data | One database, easy transactions | Data per service, distributed consistency |
| Best team size | Small to medium | Large, multiple autonomous teams |
Pros:
npm start. New developers are productive on day one.Cons:
Pros:
Cons:
Say you're running an online store — this is close to the kind of system I've worked on with Lightfunnels. As a modular monolith, it might have internal modules for catalog, cart, orders, payments, and notifications, all in one app with one database.
If that store grows to the point where the payment path needs to scale and deploy independently — because payment traffic spikes during sales and a separate team owns it — a sensible first split is:
┌──────────────┐
│ API Gateway │
└──────┬───────┘
┌──────────────┼──────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌──────────────┐
│ Catalog │ │ Orders │ │ Payments │
│ service │ │ service │ │ service │
└─────┬─────┘ └─────┬─────┘ └──────┬───────┘
▼ ▼ ▼
catalog DB orders DB payments DB
│
▼ (async event)
┌────────────────┐
│ Notifications │
│ service │
└────────────────┘
Notice you don't split everything at once. You extract the service with the clearest independent scaling and ownership need — payments — and leave the rest in the monolith until they earn their own split. This is how you migrate: incrementally, driven by real pressure, not up front.
Choose a monolith when:
Choose microservices when:
The honest summary: microservices solve organizational and scaling problems, not code-quality problems. A messy monolith becomes a messy distributed system — and now it's messy and hard to debug. Get the structure right first.
The framing of "monolith vs microservices" hides the option that's right for most teams: a modular monolith. It deploys as one unit — so you keep the simplicity, the fast internal calls, and the easy transactions — but internally it's organized into clear modules with enforced boundaries. Each module owns its domain, exposes a defined interface, and doesn't reach into another module's internals.
src/
modules/
catalog/ # owns catalog logic + data access; exposes a clean API
orders/
payments/
notifications/
shared/ # cross-cutting: auth, logging, config
The payoff is that you get the productivity of a monolith today and an easy migration path tomorrow. When one module genuinely needs to become its own service, the boundary is already there — you extract it along a seam that already exists, rather than untangling a knot. Nearly every successful microservices migration I've seen started as a well-structured monolith. The ones that started by splitting everything up front mostly regretted it.
When teams estimate microservices, they price the code and forget the operations. A distributed system needs, at minimum: centralized logging (you can't SSH into one box to grep), distributed tracing (a single request now touches many services), service discovery, a strategy for inter-service auth, handling of partial failures and retries, and usually a container orchestrator to run it all. That's a standing cost paid every day by the whole team — not a one-time setup. If you don't have the maturity and headcount to carry it, microservices will slow you down, not speed you up. That's the real reason "start with a monolith" is the right default: you defer that cost until the benefit is real.
Serverless is a related architecture decision that often comes up in the same conversation — and it can be a great way to deploy individual services. See When Should You Use Serverless Architecture?. For the complete picture of taking an app to production and scaling it, start at the hub: Deploying, Scaling & Architecting Full-Stack Apps.
A practical map for taking a full-stack app from a working codebase to a production system that ships reliably and scales — deployment, CI/CD, performance, and architecture choices.
A step-by-step walkthrough for deploying a full-stack app to production — environment prep, choosing hosting, deploying the database, backend, and frontend, connecting them, and going live with a custom domain and HTTPS.
Build a CI/CD pipeline that makes deploys boring and safe — the full flow from git push to production, a working GitHub Actions YAML file, deployment strategies, and the best practices that keep pipelines fast and reliable.