Module System
A module is a named piece of your application with four optional hooks. The runtime starts them in dependency order and stops them in reverse.
Overview
A module is one slice of your application — users, billing, notifications — packaged as an object with a name and some lifecycle hooks.
A lifecycle hook is a method the framework calls at a known moment. You do not call it yourself. You write onReady, and the runtime calls it once everything the module needs is up.
The point of modules is ordering and cleanup. If billing needs the database, you say so once, and the runtime guarantees the database starts first and shuts down last. You never write that ordering by hand.
IN PLAIN WORDS
A module is a box with a label and four buttons: set yourself up, you are live, start winding down, release everything. The framework presses the buttons in the right order. You decide what each one does.
Everything on this page lives in one package:
These docs follow the framework source. If an export shown here is missing from the version you installed, update to the latest @zudojs release.
The Module Contract
Module is the interface every module satisfies. Only id and name are required; every hook is optional.
WATCH OUT
These four names are the only hook names the lifecycle engine looks for. A method called initialize, start, stop or destroy is ignored in silence — no warning, no error, it simply never runs.
If you prefer classes, BaseModule is an abstract class that implements the interface with empty hooks. Extend it and override only the ones you want.
Defining a Module
You do not hand the framework a module instance. You hand it a definition: an id, a name, and a factory function that can build the module when asked.
defineModule validates that description and freezes it. It does not call the factory. That happens later, when the runtime loads modules — and again on every restart, so a restarted application gets clean instances instead of reusing stopped ones.
A complete module file:
usersModule is now a frozen object holding the id, the name and the factory. Nothing has run yet.
The options accepted by defineModule:
| Option | What it does | Notes |
|---|---|---|
id | Unique identifier used for lookups and dependencies | Required |
name | Human-readable label used in logs | Required |
factory | Function that builds the module instance | Required; called once per application start |
dependencies | Ids this module needs, as strings or objects | Drives startup order |
version, metadata | Semantic version and free-form descriptive data | Optional |
options | Settings passed to the factory | Deep-frozen before use |
autoLoad | Whether the runtime loads it without being asked | Defaults to true |
Registering and Running Modules
Pass your definitions to createApplication under modules. It builds the container, configuration, logger and runtime, and returns an Application you can start and stop.
A complete entry point, using the module file above:
You should see the logger line Users loaded 1 user(s), then running, then stopped.
createApplication returns a promise, so it needs await. Pass autoStart: true and it starts before returning, which saves you the separate app.start() call.
TIP
app.state tells you exactly where you are: created, initializing, initialized, starting, running, stopping, stopped or failed. Log it when a startup problem is hard to pin down.
Dependencies and Order
List the ids your module needs under dependencies. The runtime sorts the modules so that a module's dependencies are always initialized before it, and shut down after it.
A dependency can also be an object, which lets you mark it optional or attach a version constraint: { id: "users", optional: true }. An optional dependency that is missing is skipped instead of failing startup.
The ordering algorithm is exported on its own, so you can see the result without starting anything. This is a complete script:
Output:
Shutdown is the exact reverse of startup. That is what makes cleanup safe: payments is finished with orders before orders tears anything down.
DANGER
If two modules depend on each other, directly or through a chain, there is no valid order and CircularModuleDependencyError is thrown. Break the cycle by moving the shared piece into a third module that both depend on.
The Four Hooks
Each hook receives the module's ModuleContext and may return a promise, which the runtime awaits before moving on.
| Hook | When it runs | Put this in it |
|---|---|---|
onInitialize | Initialize step, in dependency order | Open connections, read config, register services |
onReady | Start step, after every module has initialized | Begin listening, start timers, consume queues |
onShutdown | Stop step, in reverse order | Stop accepting new work, drain in-flight work |
onDestroy | Destroy step, last | Close connections, free file handles and memory |
START STOP users.onInitialize payments.onShutdown orders.onInitialize orders.onShutdown payments.onInitialize users.onShutdown users.onReady payments.onDestroy orders.onReady orders.onDestroy payments.onReady users.onDestroy
The split between onInitialize and onReady matters. During onInitialize other modules may not exist yet. By onReady they all do, so that is where cross-module work belongs.
The Module Context
Every hook is handed a ModuleContext. It is deliberately narrow: a module gets capabilities, not a handle on the whole runtime.
| Member | What it gives you | Notes |
|---|---|---|
id, name, version | This module's own identity | Read-only |
options, metadata | Whatever you passed to defineModule | Deep-frozen |
logger | A logger already tagged with this module | info, warn, error, and so on |
configuration | The configuration manager | Shared across the application |
getConfig(path) | One config value, or undefined | Use requireConfig to fail loudly instead |
application | The application context | Container, config snapshot, module registry |
hasModule(id), getModuleContext(id) | Reach a declared dependency | Undeclared ids return false / throw |
WATCH OUT
Reaching for a module you did not declare as a dependency throws MissingModuleDependencyError. This is on purpose: a hidden dependency would break the startup order, so the framework refuses to let you create one.
Common Mistakes
-
Naming a hook
startinstead ofonReadyThe application starts, nothing happens, and there is no error. Rename it to one of the four supported hook names.
-
Forgetting
awaitoncreateApplicationYou get a promise, and
app.startis not a function. Addawait, or use top-levelawaitin an ES module. -
Using another module without declaring it
MissingModuleDependencyError. Add the id todependenciesso the ordering can account for it. -
Doing cross-module work in
onInitializeThe other module may not be loaded yet. Move it to
onReady, which runs only after every module has initialized.