Configuration
Overview
The config system provides a pluggable, priority-based configuration pipeline with schema-driven defaults, scope cascade, and boot-time validation.
Config Sources
Configuration values come from sources — classes that extend ConfigSource and are registered in the ConfigSourcePool:
@Injectable()
export abstract class ConfigSource {
abstract readonly name: string;
abstract readonly priority: number;
abstract get(path: string, scope?: ScopeLevel): unknown | undefined;
}Higher priority sources are consulted first. The built-in sources are:
| Source | Priority | Package |
|---|---|---|
EnvConfigSource | 1000 | @modularityjs/config-env |
DefaultsSource | 0 | @modularityjs/config (built-in) |
You can add custom sources at any priority level by extending ConfigSource and registering in ConfigSourcePool.
Reading Config Values
Inject ConfigService and call get():
@Injectable()
class MyService {
constructor(@Inject(ConfigService) private readonly config: ConfigService) {}
getPort(): number {
return this.config.get<number>('app/port') ?? 3000;
}
}getOrThrow() throws if no value is found:
const secret = this.config.getOrThrow<string>('app/secret');Schema Defaults
Register defaults via the ConfigSchemaPool:
@Module({
name: 'my-module',
imports: [ConfigModule],
pools: [
{
pool: ConfigSchemaPool,
key: 'app/port',
useValue: {
path: 'app/port',
type: 'number',
default: 3000,
description: 'HTTP server port',
},
},
],
})
class MyModule {}The DefaultsSource (priority 0) reads these entries and serves them as the lowest-priority fallback.
Environment Variables
With ConfigEnvModule, environment variables override all other sources. The naming convention:
| Config Path | Environment Variable |
|---|---|
app/port | MODULARITYJS__APP__PORT |
cache/ttl | MODULARITYJS__CACHE__TTL |
app/debug-mode | MODULARITYJS__APP__DEBUG_MODE |
Slashes become __, hyphens become _, everything is uppercased, prefixed with MODULARITYJS__.
Values are auto-parsed: true/false become booleans, numeric strings become numbers, JSON objects are parsed.
Boot-Time Validation
At boot, the config system validates all registered schema entries:
- Type checking: Resolved values must match their declared type (
string,number,boolean,json) - Required enforcement: Entries with
required: truemust have a value from some source
{
pool: ConfigSchemaPool,
key: 'app/secret',
useValue: {
path: 'app/secret',
type: 'string',
required: true, // boot fails if no source provides this
},
}All validation failures are reported together so you can fix everything in one pass.
Resolution Pipeline
When config.get('path') is called:
- Get all config sources, sorted by priority (descending)
- For each source:
- If scopes are active, walk the scope chain from most-specific to least-specific
- Then try unscoped resolution
- Return the first non-undefined value
- If no source has a value, return
undefined