Skip to main content
A module is the unit of composition in Ooneex: a single ModuleType object that declares what a domain owns. You don’t write glue code to wire controllers, entities, or jobs together — you list the classes in the appropriate array, and the framework resolves and wires each one through the dependency injection container. Composing a module is the act of collecting a domain’s artifacts in one place; registering it is the act of exposing them to the running app.

How it works

A ModuleType from @ooneex/module has one array per artifact kind. Listing a class in an array is the declaration — the framework reads the module, registers each class with the container, and applies its decorators (routing for controllers, @decorator.middleware() for middleware, and so on).
import type { ModuleType } from "@ooneex/module";

export const OrderModule: ModuleType = {
  controllers: [],   // HTTP and WebSocket controllers
  entities: [],      // database entities (TypeORM-backed)
  middlewares: [],   // request/socket middleware
  cronJobs: [],      // scheduled jobs
  events: [],        // pub/sub event handlers
};
ArrayWhat belongs hereResolved as
controllersHTTP controllers from @ooneex/controller and WebSocket controllers from @ooneex/socket.Routes registered from each @Route.* decorator.
entitiesTypeORM-backed database entities the domain persists.Registered with the data source for migrations and repositories.
middlewaresRequest and socket middleware that intercept the domain’s traffic.Registered via @decorator.middleware() into the pipeline.
cronJobsScheduled jobs the domain runs on a timer.Registered with the scheduler from each job’s cron expression.
eventsPub/sub event handlers the domain reacts to or emits.Registered as subscribers on the event bus.
Every class you list is resolved through the 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.

Composing a module

Build a module up one array at a time. Each array maps to a kind of artifact the domain owns — nothing more.

controllers

Both HTTP and WebSocket controllers go in controllers. The framework registers each one’s routes from its @Route.* decorators.
import { OrderCreateController } from "./controllers/OrderCreateController";
import { OrderListController } from "./controllers/OrderListController";
import { OrderStreamController } from "./controllers/OrderStreamController"; // WebSocket

export const OrderModule: ModuleType = {
  controllers: [OrderCreateController, OrderListController, OrderStreamController],
  entities: [],
  middlewares: [],
  cronJobs: [],
  events: [],
};

entities

List the TypeORM-backed entities the domain persists. Registering them here makes them part of the data source, so repositories and migrations can find them.
import { OrderEntity } from "./entities/OrderEntity";
import { OrderItemEntity } from "./entities/OrderItemEntity";

export const OrderModule: ModuleType = {
  controllers: [OrderCreateController, OrderListController, OrderStreamController],
  entities: [OrderEntity, OrderItemEntity],
  middlewares: [],
  cronJobs: [],
  events: [],
};

middlewares

Middleware that intercepts the domain’s requests or socket connections goes here. The framework registers each via its @decorator.middleware().
import { OrderValidationMiddleware } from "./middlewares/OrderValidationMiddleware";

export const OrderModule: ModuleType = {
  controllers: [OrderCreateController, OrderListController, OrderStreamController],
  entities: [OrderEntity, OrderItemEntity],
  middlewares: [OrderValidationMiddleware],
  cronJobs: [],
  events: [],
};

cronJobs

Scheduled work the domain runs on a timer belongs in cronJobs. Each job is registered with the scheduler from its cron expression.
import { OrderCleanupCron } from "./crons/OrderCleanupCron";

export const OrderModule: ModuleType = {
  controllers: [OrderCreateController, OrderListController, OrderStreamController],
  entities: [OrderEntity, OrderItemEntity],
  middlewares: [OrderValidationMiddleware],
  cronJobs: [OrderCleanupCron],
  events: [],
};

events

Pub/sub handlers the domain reacts to — or emits — go in events. Each is registered as a subscriber on the event bus.
import { OrderCreatedEvent } from "./events/OrderCreatedEvent";
import { OrderShippedEvent } from "./events/OrderShippedEvent";

export const OrderModule: ModuleType = {
  controllers: [OrderCreateController, OrderListController, OrderStreamController],
  entities: [OrderEntity, OrderItemEntity],
  middlewares: [OrderValidationMiddleware],
  cronJobs: [OrderCleanupCron],
  events: [OrderCreatedEvent, OrderShippedEvent],
};
The result is a complete vertical slice: one object that declares every artifact the order domain owns, with no wiring code in between.

Registering a module

Composing a module declares its artifacts; registering it exposes them to the running app. A module is registered into a destination module. The app destination registers a new module into both AppModule and SharedModule — wiring handled for you by module:create. Once registered, the app has access to every artifact the registered modules declare.
import type { ModuleType } from "@ooneex/module";
import { OrderModule } from "../modules/order";

export const AppModule: ModuleType = {
  controllers: [...OrderModule.controllers],
  entities: [...OrderModule.entities],
  middlewares: [...OrderModule.middlewares],
  cronJobs: [...OrderModule.cronJobs],
  events: [...OrderModule.events],
};
From there, resolution flows through the DI container. The app reads each registered module, registers every listed class with the container, and the framework applies the decorators: controllers join the router, middleware joins the pipeline, cron jobs join the scheduler, events join the bus, and entities join the data source. Nothing is resolved until it is declared in a module that has been registered into the app.

Best practices

  • One domain per module. A module is a vertical slice. Keep the order domain in OrderModule and the auth domain in AuthModule — don’t mix unrelated concerns in a single object.
  • List only what the domain owns. An array entry is a declaration of ownership. If a class belongs to another domain, it should be declared in that domain’s module, not duplicated into yours.
  • Prefer the generators. Scaffolding artifacts with the :create commands auto-registers them in the right array, so the module stays in sync without manual edits.
  • Keep cross-module dependencies explicit via shared. When two domains need the same artifact, declare it once in the shared module rather than reaching across module boundaries — SharedModule is the seam for cross-cutting code.
  • Let registration handle exposure. Compose the domain in its own module and register it through app; avoid hand-wiring artifacts into AppModule outside the generated flow.

Learn more

  • Module overview — what a module is and why Ooneex composes by domain.
  • Module structure — how a module’s files are laid out on disk.
  • Dependency injection — how the container resolves and wires declared artifacts.
  • Routing — how @Route.* decorators turn controllers into routes.
  • Events — writing the pub/sub handlers you list in events.
  • WebSockets — socket controllers and middleware that share the controllers and middlewares arrays.