Skip to main content
A microservice is composed exactly like a module: it exports a single <PascalName>Module of type ModuleType from @talosjs/module that declares what the service owns. You list artifact classes in the appropriate array, and the framework resolves and wires each one through the dependency injection container. Controllers join the router, middleware joins the pipeline, cron jobs join the scheduler, events join the bus, and entities join the data source. The one thing that sets a microservice apart is exposure. A plain module is registered into AppModule/SharedModule so the app process can resolve it. A microservice is not. It runs as its own process, with its own DI container, its own port, and its own Dockerfile, and its src/index.ts entrypoint boots the service directly from its own module. Composition is identical; what changes is who runs it.

How it works

The service’s ModuleType has one array per artifact kind. Listing a class is the declaration. Within the microservice’s process the framework reads the module, registers each class with the container, and applies its decorators.
Every class you list is resolved through the microservice’s DI container. That is what makes a bare array entry enough: the container constructs the class, injects its dependencies, and the framework reads its decorators to plug it into routing, the scheduler, or the event bus. It is the same resolution model as any Talos app, just running in a process of its own.

Composing the service module

Build the module up one array at a time. Each array maps to a kind of artifact the service owns, nothing more. A complete BillingModule for a self-contained billing service looks like this.

controllers

Both HTTP and WebSocket controllers go in controllers. The service registers each one’s routes from its @Route.* decorators.

entities

List the TypeORM entities the service persists. A microservice owns its own database, so these entities back its own data source, not a shared one.

middlewares

Middleware that intercepts the service’s requests or socket connections goes here, registered via its @decorator.middleware().

cronJobs

Scheduled work the service runs on a timer belongs in cronJobs. Each job is registered with the scheduler from its cron expression, and runs inside this service’s process.

events

Pub/sub handlers the service reacts to (or emits) go in events. Each is registered as a subscriber on the event bus.
The result is a complete vertical slice: one object that declares every artifact the billing service owns, with no wiring code in between.

Booting from the service’s own module

This is where a microservice diverges from a plain module. A module declares its artifacts and is then registered into a destination module so the app process can resolve them; see module composition, where OrderModule is spread into AppModule. A microservice skips that step entirely. It is never registered into AppModule or SharedModule. Instead, the template wires the microservice’s src/index.ts entrypoint to boot the service from its own module, so the service’s process resolves BillingModule directly.
At startup, src/OnAppStart.ts runs so the service can perform any setup it needs (connecting to its data source, warming caches) before it begins handling traffic. From there, resolution flows through the microservice’s DI container exactly as it would in any Talos app: every class listed in BillingModule is registered with the container, and the framework applies the decorators. Controllers join the router (see routing), middleware joins the pipeline, cron jobs join the scheduler, events join the bus, and entities join the data source.

Keeping the service isolated

A microservice is a vertical slice that owns a single domain end to end. Keep billing in BillingModule; if a concern belongs to another domain, it belongs to that domain’s service. Declare the service’s own entities and let it own its database. A microservice should never reach into another service’s data source or share tables. When the billing service needs data from another service, call it over the network (see networking) rather than importing that service’s controllers, entities, or repositories. Every array entry is a declaration of ownership, and resolution happens inside this process, so list only the classes this service actually runs. Two habits keep the isolated-process model intact. A microservice boots from its own module, so don’t wire it into AppModule. And lean on the generators: scaffolding with the :create commands (controller:create, service:create, entity:create, and so on) targets the microservice’s module and writes into its src/, which keeps the module in sync without manual edits.

Learn more