Bootstrap
Bootstrap is the bridge between "I have a class" and "I have a running service". Three steps, in this order.
Step 1 — Application.create(...)
Two overloads:
// Form 1: module-first.
Application.create(AppModule, options?)
// Form 2: options-only — matches the canonical README example.
Application.create({ modules: [AppModule], ...otherOptions })
Related helpers:
import { createApp, createAndStartApp } from '@omnitron-dev/titan';
createApp // sync new Application(options) — constructor only;
// core/extra modules register lazily on start()
createAndStartApp // create + start in one call (see Step 2)
createAppis notApplication.create. ThecreateAppexported from the package root is a thin sync wrapper over the constructor (new Application(options)). It does not register a main module, core modules, or run auto-discovery — those happen inside the asyncApplication.createfactory (or lazily duringstart()). UseApplication.create({ modules: [...] })when you want the full wiring up front. (There is a separatecreateAppin the@omnitron-dev/titan/applicationSimple API that is bound toApplication.create— but the root-barrel export is the sync one.)
What happens inside create:
- Application instance —
new Application(options)builds the instance with its container, lifecycle state machine, module registry, and shutdown coordinator. - Main module registration — if you passed a module class as the first argument, it's registered.
- Core modules — unless
disableCoreModules: true,ConfigModuleandLoggerModuleare registered. - Auto-discovery — if
autoDiscovery: trueorscanPathsis set, modules are discovered from the filesystem (off by default). - Additional modules — anything in
options.modulesis registered next; anything inoptions.importsis imported by token. - Root-level providers — if
options.providersis set, a synthetic root module wraps them and registers each in the container. - Eager singleton init —
container.eagerlyInitialize()runs so cross-module dependencies are resolvable when lifecycle hooks fire.
After create returns, the container is fully wired but no
lifecycle hook has fired. The state is Created.
Step 2 — app.start()
await app.start();
What happens, in order:
- State transition —
Created → Starting. The lifecycle state machine takes over. onInitphase — every provider that implementsOnInit(or has a@PostConstructmethod) runs in dependency order.onStartphase — every provider that implementsOnStartruns in dependency order. This is where most modules open external connections.- State transition —
Starting → Started.
After start resolves, every provider has had its onInit and
onStart called. The application is live.
Implementation note. The phases above are the contract the framework guarantees, not a literal 1:1 of the call order inside
_doStart. In practice the Nexus container fires most provideronInit/@PostConstructhooks during the eager singleton initialisation that runs insideApplication.create(and a finalcontainer.initialize()sweep at the tail ofstart), while moduleonStarthooks run duringstart. What you can rely on: a provider'sonInitcompletes before its ownonStart, and both respect dependency order. Don't depend on the absolute wall-clock interleaving of unrelated providers' phases.
A failure in any phase aborts the start, rolls back modules that
reached onStart (in reverse order) via their onStop, disposes the
container, transitions to Failed, and rejects with the original
error.
Step 3 — running
Between start and stop, the application does almost nothing
itself. The kernel waits, listening for:
- A
stop()call. - A bound OS signal (
SIGTERM,SIGINT,SIGHUP). - An uncaught exception or unhandled rejection (configurable).
When one of these fires, the kernel transitions to Stopping and
runs the shutdown coordinator (see Shutdown).
Useful variants
createAndStartApp — when you want both calls together
import { createAndStartApp } from '@omnitron-dev/titan';
const app = await createAndStartApp({
name: 'my-app',
version: '1.0.0',
modules: [AppModule],
});
Signature (from the actual source):
createAndStartApp(options?: {
name?: string;
version?: string;
config?: any;
modules?: IModule[];
}): Promise<IApplication>;
The function calls createApp(...), registers any modules in the
list via app.use(), then awaits app.start(). Use when you don't
need the intermediate Created state.
titan() — the simplest entry point
The Simple API in @omnitron-dev/titan/application (re-exported as
the default export of the package) wraps everything:
import titan from '@omnitron-dev/titan/application';
// Either a module…
const app = await titan(AppModule);
// …or a bare config, which builds the module for you:
const appFromConfig = await titan({ port: 3000, redis: 'localhost:6379' });
Smart defaults: pretty logging in dev, JSON in prod, graceful
shutdown enabled, scan paths set. Use for prototypes and small apps;
for production stacks, use Application.create explicitly so the
configuration is visible.
Bootstrap diagnostics
Set debug: true or logging: { level: 'debug' } to see each phase
in the structured log. Look for phase and provider fields to
trace lifecycle ordering.
Anti-patterns
- Calling
starttwice. It is a no-op after the first successful start, but a reliance on idempotency hides bugs. - Logging from constructors. Constructors run during container
resolution, before
LoggerModulehas fully started. UseonInitoronStartinstead. - Opening connections in constructors. Constructors should be
cheap. Open expensive resources in
onStart. - Catching
starterrors and continuing. A failedstartleaves the app inFailedstate. Re-throw and let the supervisor restart the process.
→ Next: Lifecycle.