Skip to main content

Dependency Injection

The DI container is the single source of truth for every object in a Titan application. It is named Nexus and ships in the @omnitron-dev/titan/nexus subpath.

import { Container, createContainer } from '@omnitron-dev/titan/nexus';

This page is the entry point. Mechanics in the per-page references.

What a DI container does

Three jobs:

  1. Registration — you tell the container "this token maps to this provider".
  2. Resolution — you ask the container for a token; it constructs (or returns the cached instance of) whatever the provider says.
  3. Lifetime — the container decides when a constructed instance is reused, scoped, or disposed.

In a Titan app, you almost never call container.resolve yourself. You declare a class with constructor dependencies; the container walks the constructor signature, resolves each dependency, and injects them.

@Service({ name: 'users' })
export class UsersService {
// The container resolves Database and LoggerService from the
// module's import graph and injects them here.
constructor(
private readonly db: Database,
private readonly logger: LoggerService,
) {}
}

What Nexus brings beyond a basic DI container

  • Multi-tokens — a single token can hold multiple providers (createMultiToken<T[]>('Validators')).
  • Contextual injectionContextManager + strategies (RoleBasedStrategy, EnvironmentStrategy, FeatureFlagStrategy, TenantStrategy) resolve different providers per request / tenant / feature flag.
  • DI middlewareRetryMiddleware, CachingMiddleware, LoggingMiddleware, RateLimitMiddleware, CircuitBreakerMiddleware, ValidationMiddleware, TransactionMiddleware wrap resolution itself.
  • Cross-platform — Node, Bun, Deno, browser. Runtime detection via Runtime, detectRuntime(), isBun(), isNode(), isDeno(), isBrowser().
  • Lifecycle hooksLifecycleManager, LifecycleEvent, LifecycleObserver, plus built-in observers (AuditObserver, MemoryObserver, PerformanceObserver).

You do not need to learn a new mental model — Nexus is "DI like you already know" — but the deeper pages cover the parts that go beyond the basics.

A complete example

import {
Container,
createContainer,
createToken,
Scope,
} from '@omnitron-dev/titan/nexus';

interface ILogger {
info(message: string): void;
}

const LOGGER = createToken<ILogger>('Logger');

class ConsoleLogger implements ILogger {
info(message: string) { console.log(message); }
}

class UsersService {
constructor(private readonly logger: ILogger) {}
list() { this.logger.info('listing users'); return []; }
}

const container = createContainer();

container.register(LOGGER, {
useClass: ConsoleLogger,
scope: Scope.Singleton,
});

container.register(UsersService, {
useClass: UsersService,
inject: [LOGGER],
scope: Scope.Singleton,
});

const users = container.resolve(UsersService);
users.list();

In a Titan app, you do not write container.register calls — @Module, @Service, and @Injectable translate to the same registrations behind the scenes.

What the container guarantees

GuaranteeExplanation
Topological resolutionDependencies are constructed before dependents
Cycle detectionThrows CircularDependencyError at resolution time
Scope correctnessA Singleton returns the same instance every time
Disposalcontainer.dispose() runs lifecycle teardown
Type safetyTokens are typed; resolve(LOGGER) returns ILogger
Async resolutionresolveAsync<T>(token) for async providers

Error types exposed

The Nexus error hierarchy (NexusError → specialised subclasses):

Error classWhen
RegistrationErrorInvalid provider registration
ResolutionErrorGeneric resolution failure
DependencyNotFoundErrorProvider for token not registered
CircularDependencyErrorConstructor cycle detected
ScopeMismatchErrorNarrower scope captured in wider scope
NotInjectableErrorClass missing the @Injectable decoration
DuplicateRegistrationErrorTwo providers for the same single token
InvalidProviderErrorProvider definition malformed
InitializationErrorProvider construction threw
AsyncResolutionErrorSync resolve() on an async provider
ContainerDisposedErrorResolve after dispose
DisposalErrorError during teardown
NexusAggregateErrorMultiple errors batched (e.g. parallel disposal)
ModuleErrorModule-level configuration error

Read the deep pages

TopicWhen to read
ProvidersThe five provider types and when to use each
ScopesTransient / Singleton / Scoped / Request
TokensClass, symbol, multi-, lazy, async, optional tokens
Multi-injectionPlugin patterns, middleware chains
Contextual injectionPer-request / per-tenant / per-environment
MiddlewareWrapping resolution itself
Circular DependenciesDiagnosing and fixing cycles
DevToolsInspecting the container at runtime

→ Start with Providers.