<PascalName>Module of type ModuleType from @ooneex/module with controllers, entities, middlewares, cronJobs, and events — but instead of being composed into the single app process, it is deployed and started on its own. The main app/API talks to it over the network. The difference is deployment and networking, not the programming model.
Like a regular module, a microservice lives under modules/<kebab-name>/. Unlike a regular module, it is not registered into AppModule/SharedModule; it owns its own entrypoint, start hook, Dockerfile, config, port, and roles, and it is declared as a remote dependency the app reaches by URL.
Why microservices
- Same programming model. A microservice exports the same
ModuleTypeshape from@ooneex/module— controllers, entities, middlewares, cronJobs, events. If you can write a module, you can write a microservice. - Independent deployment. It is its own process with its own
Dockerfileandsrc/index.tsentrypoint, so it ships, scales, and restarts on its own schedule — separate from the API. - Its own runtime config. Each microservice carries a distinct
.env.ymlon its own port (the API runs on3000; each microservice takes the next free port starting at3001) and its ownroles.yml. - Network boundary. The app calls the microservice over HTTP, so a fault or slow deploy stays contained behind that boundary instead of taking down the whole app process.
- One CLI to manage it. Scaffold and tear down a microservice with
ooneex microservice:createandooneex microservice:remove— no manual wiring.
How it works
A microservice reuses the module system: it exports a<PascalName>Module of type ModuleType, the same as a regular module. What changes is everything around the module. It gets its own entrypoint and start hook, runs as its own containerized process on its own port, and is reached by the app over HTTP rather than being composed into AppModule/SharedModule. The app declares it in modules/app/app.yml under a microservices: list and resolves its address from an environment variable.
| Aspect | Regular module | Microservice |
|---|---|---|
| Programming model | ModuleType from @ooneex/module | Same ModuleType from @ooneex/module |
| Deployment | Composed into the single app process | Its own process, with its own Dockerfile and src/index.ts entrypoint |
| Networking | In-process function calls | App reaches it over HTTP |
| Registration | Added to AppModule / SharedModule | Declared in modules/app/app.yml under microservices:, wired by env var |
| Port | Shares the API on 3000 | Its own port, next free starting at 3001 |
| Config & roles | Shares the app config | Owns a distinct .env.yml and roles.yml |
modules/app/app.yml as a named remote whose URL comes from the environment:
ModuleType, same @ooneex/module import:
src/index.ts, src/OnAppStart.ts, Dockerfile, .env.yml, roles.yml — are what turn that module into a self-contained, deployable service.
When to reach for one
Reach for a plain module by default: it is composed into the app process and called in-process, which is simpler to build, test, and deploy. Reach for a microservice when a piece of the system needs to deploy, scale, or fail independently of the API — a separate runtime, its own port, its own container, its own release cadence. The code inside stays aModuleType either way, so the choice is about boundaries, not about rewriting your logic.
Next steps
- Structure — the files a microservice owns: entrypoint, start hook,
Dockerfile,.env.yml,roles.yml. - Composition — exporting the
ModuleType(controllers, entities, middlewares, cronJobs, events). - Networking — declaring it in
app.yml, ports, and wiring the URL through env. - Create — scaffold one with
ooneex microservice:create. - Remove — tear one down with
ooneex microservice:remove. - Module overview — the regular, in-process module it is built on.
- Deployment — shipping the API and its microservices.