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 })
Aliases:
import { createApp, createAndStartApp } from '@omnitron-dev/titan';
createApp // same as Application.create
createAndStartApp // create + start in one call (see Step 2)
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.
A failure in any phase aborts the start, fires onStop/onDestroy
for providers that already ran, and rejects with a typed 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';
const app = await titan(AppModule);
// or
const app = 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.