Multi Service Architecture in One Process

Routers Behind One Door
Five services once meant five processes and five deploys, until this design folded all of it into one process to keep them apart.
Isolated routers run side by side on separate ports in that one process, sharing memory instead of paying for network hops on every call, so services stay independent at the request level while the runtime underneath stays exactly the same for them.
Each router owns its own route table, middleware stack, file watcher, and optional template engine, so a service configures on its own terms, and one entry point spawns as many routers as memory can hold, each listening on its own port and watching its own directory, which turns shared code and shared state into a plain local call rather than a remote network one.
Note
The framework is open source on GitHub at NeaByteLab/Deserve, with full documentation at docs-deserve.neabyte.com. MIT licensed and installable from JSR, it ships zero dependencies and runs isolated routers in one Deno process with shared memory.
The Process Boundary Problem
Most deployment patterns for many services split the work across separate processes or containers, and every one of them carries its own copy of shared code, its own network stack, and its own deploy pipeline, so the simple act of sharing a session store suddenly means reaching for Redis or a broker and paying a network hop for state that used to sit right next door.
Running five services as five processes means five deploys and five copies of the same utility code duplicated across each one, and any shared state now lives behind a network hop, so separation shows up as latency on every single call across them.
The gap was a model that keeps services isolated per request while sharing the process underneath, so code and state stay local.

Five Deploys for Five Services
Splitting services into separate processes looks clean on a diagram, yet each split quietly duplicates shared code, forces state behind a network hop, and adds another pipeline to babysit, so operational weight grows with every service that ships.
A fault in one service can ripple through shared infrastructure like a database, and that radius widens with each new process.
- Running services as separate processes duplicates shared code across each deploy.
- Sharing state between services demands Redis, HTTP calls, or a message broker.
- Each process carries its own network stack, its own container, and its own pipeline.
- A fault in one service can ripple through shared infrastructure like a database.
- Operational overhead scales linearly with the number of services running.
Many Services, Single Runtime
The target was one process hosting many services without blurring the line between them, so each one still owns its own routes.
Beyond isolation, the goal was to make shared code, shared state, and shared infrastructure feel local, reachable through memory at the speed of a function call, while keeping a clean exit so any service that outgrows the process can move out later.
- Run multiple isolated routers side by side in a single Deno process.
- Give each router its own routes, middleware, file watcher, and port.
- Share code, state, and infrastructure through memory instead of network hops.
- Contain faults at the request level so a crash in one service never breaks others.
- Scale out by extracting any service into its own process without changing route files.
Isolation at the Request Level
The strategy is router-level isolation inside one shared process, where each router keeps its own radix-tree route table, its own middleware stack, its own file watcher, and its own optional template engine, so two services can run completely different middleware and configuration while sitting in the same memory space without ever leaking behavior into one another at all.
Faults are contained at two levels here, since a throw in one handler becomes an error for that request while the rest keep going.
A deeper fault that escapes a handler is trapped process-wide by a guard and surfaced as an event rather than a shutdown, so the shared process stays alive and every other service keeps on answering incoming requests through the entire incident.

Promise All Spawns the Fleet
Every service follows the same folder convention, with routes in its own directory, shared code in a shared folder, and one entry point wiring it all together, so anyone opening the project sees where routes and shared modules live right at a glance.
// One Router per service, one port per router
const api = new Router({
routes: { directory: './services/api/routes' }
})
const auth = new Router({
routes: { directory: './services/auth/routes' }
})
const web = new Router({
routes: { directory: './services/web/routes' },
views: { directory: './services/web/views' }
})
// Serve every service together on its own port
await Promise.all([
api.serve(3001),
auth.serve(3002),
web.serve(3003)
])
Each service carries its own file watcher, so saving a file reloads only the service that owns that directory while the others keep serving without interruption, and a shared session store shows the payoff, where one service writes sessions on login and another reads them to authenticate requests through the same in-memory map, no Redis and no HTTP call between them.
A shared cache with a time limit erases duplicate work, and a shared logger tags every request by the service that produced it.

Wide Surface, Lean Core
What shipped is a small core with a wide surface, where per-router isolation, request-level fault containment, and in-memory sharing all land in one process, and any service can still be extracted into its own separate process later on down the road.
One container now carries every port, a reverse proxy routes by domain, and hot reload touches only the service that changed.
| Feature | Delivered |
|---|---|
| Isolation | Per-router route table, middleware, watcher, and engine |
| Fault Containment | Request-level errors contained, process guard traps exits |
| Shared State | In-memory session store, event bus, and cache across services |
| Hot Reload | Per-service file watcher reloads only the changed service |
| Middleware | Per-service or shared, with labeled wrapping for error logs |
| Deployment | One Docker container, all ports, reverse proxy by domain |
| Scaling Out | Extract any service to its own process without route changes |
Shared State Is Not Free Lunch
Router isolation keeps a fault inside one service, yet a shared map is the opposite of isolation, since every service reads it.
One service that writes bad data into a shared store hands the very same bad data to every other reader, so the fix is to let one module own each store and validate every write at its own edge, the way a session module is the only door to the map.
Coupling then moves from the network layer down to the data layer but stays controlled, and the honest rule is to reach for shared state only when speed matters and services genuinely belong together, then fall back to a plain HTTP hop between them whenever a much cleaner service boundary turns out to be worth far more than the raw speed of a shared memory object.
One Process, Zero Downtime
What began as a way to stop duplicating deploys turned into isolated routers running side by side in one process, each owning its own routes, middleware, and watcher, while sharing code and state through memory instead of paying for network hops.
The editor stays quiet, one entry point spawns the fleet, and a service that outgrows the process moves out with no route change.