The Modular Monolith
"Modular monolith" is the phrase Watt's documentation leads with, and it is doing real work rather than acting as a slogan. This page explains what the term means, why it describes Watt accurately, and — more usefully — when you should stop using one.
If you want to build one rather than reason about one, go to Build a modular monolith, which walks through a complete multi-application example.
The term
A monolith is one deployment unit. A modular system has enforced internal boundaries. Most architectures give you one or the other:
- A conventional monolith is one deployment unit with advisory boundaries. Modules are separated by directory layout and code review. Nothing stops one part importing another's internals, and over a few years, something always does.
- Microservices give you enforced boundaries — you physically cannot import across a network — at the cost of one deployment unit per boundary, and all the machinery that implies.
A modular monolith is the combination that is usually assumed to be unavailable: one deployment unit, enforced boundaries. The boundaries are real, not conventional, but you still ship one thing.
Why Watt is one, mechanically
The claim rests on the worker thread model. Each application runs in its own V8 isolate, so the boundary between applications is enforced by the runtime rather than by discipline:
- There is no import path from one application to another's code. Separate module registries.
- There is no shared object graph. Separate heaps.
- The only way to reach another application is its HTTP interface over the mesh, at
http://<id>.plt.local.
That last point is the important one. The boundary is not merely enforced — it is enforced in the shape of a network interface. An application interacts with its neighbour exactly as it would if that neighbour were a service in another datacentre: a URL, a request, a response, a status code.
Meanwhile the deployment story stays monolithic. One npm install, one build, one process, one
container, one thing to deploy and roll back.
The property that makes it worth doing
Most architectural decisions are hard to reverse. This one is not, and that is the actual argument for it.
Because applications already talk over HTTP, extracting one to its own deployment does not require
rewriting how it is called. The call site is a URL either way. Moving an application out means giving
it its own runtime and pointing callers at a real hostname instead of a .plt.local one. The
application's own code — routes, handlers, business logic — does not change at all.
This inverts the usual sequencing problem. The conventional advice is "start with a monolith, extract services when you need to", which is good advice with a bad failure mode: by the time you need to extract, the boundaries have eroded and the extraction is a rewrite. Watt's boundaries cannot erode, because the runtime enforces them from day one. The extraction stays cheap indefinitely.
You are deferring a decision rather than making one. You get to find out where your real service boundaries are by operating the system, instead of guessing during design.