Configuration
ConfigModule is one of the two core modules auto-loaded with every
Titan app (the other is LoggerModule). Disable with
disableCoreModules: true if you need to provide your own.
This is the entry point. Detail in:
- Sources — files, env, argv, objects, remote.
- Validation — schema-checked config at boot.
- Hot Reload —
ConfigWatcherServiceand events.
The mental model
Configuration is layered: multiple sources merge into a single typed object. Later sources override earlier ones. Optionally validated against a Zod schema.
import { ConfigModule } from '@omnitron-dev/titan/module/config';
@Module({
imports: [
ConfigModule.forRoot({
schema: AppConfigSchema,
sources: [
{ type: 'file', path: 'config/default.yaml' },
{ type: 'env', prefix: 'APP_' },
],
validateOnStartup: true,
watchForChanges: true,
}),
],
})
class AppModule {}
Reading config
Through ConfigService
import { Inject, Service } from '@omnitron-dev/titan';
import { ConfigService, CONFIG_SERVICE_TOKEN } from '@omnitron-dev/titan/module/config';
@Service({ name: 'Users' })
class UsersService {
constructor(@Inject(CONFIG_SERVICE_TOKEN) private readonly config: ConfigService) {}
@Public()
async findById(id: string) {
const ttl = this.config.get<number>('cache.ttlMs', 60_000);
// …
}
}
get<T>(path, default?) returns the typed value at the dotted path.
The default is returned if the key is missing.
Through decorators
import { Config, InjectConfig } from '@omnitron-dev/titan/module/config';
@Service({ name: 'Users' })
class UsersService {
@Config('cache.ttlMs', 60_000)
private readonly cacheTtl!: number;
@InjectConfig()
private readonly fullConfig!: AppConfig;
}
@Config(path, default?) injects a specific value. @InjectConfig()
injects the full config object.
Additional config decorators:
| Decorator | Effect |
|---|---|
@ConfigSchema(schema) | Class-level Zod schema for this class's config subtree |
@Configuration(prefix?) | Bind a class to a config prefix (auto-populates fields) |
@ConfigWatch(path) | Method runs when the watched path changes |
@ConfigDefaults({...}) | Provide defaults at class level |
@ConfigProvider(name) | Mark a class as a custom config provider |
Why validate at boot
A typed schema for your config means:
- Misconfiguration crashes at startup, not at the first call that needs the bad value.
- Type safety in code —
config.get('database.url')returns the schema's type, notstring | undefined. - Documentation lives in the schema — readers know what config the app accepts by reading the Zod schema.
When to use what
| Approach | When |
|---|---|
ConfigService.get(path) | Reading multiple unrelated keys; dynamic key paths |
@Config(path, default?) | A class needs one or two specific values |
@Configuration(prefix) | A class is a typed view of a config subtree |
| Compile-time constants | Compile-time values that don't change per env |
For values that change per environment, always use config —
hardcoded localhost:5432 in code locks you to one deployment.
Read on
- Sources — every source type and what it accepts.
- Validation — schema patterns and error handling.
- Hot Reload —
ConfigWatcherService, theconfig:changedevent.
→ Next: Sources.