NestboltNestbolt

@nestbolt/settings

Configuration

SettingsModule.forRoot() / forRootAsync() options and how the cache, defaults, and global flag interact.

forRoot

SettingsModule.forRoot() registers SettingsService and (optionally) one SETTING_<key> provider per entry in defaults:

import { SettingsModule } from "@nestbolt/settings";

@Module({
  imports: [SettingsModule.forRoot()],
})
export class AppModule {}

Options

OptionTypeDefaultDescription
defaultsSettingDefinition[]undefinedSettings to seed on module init and/or expose via @Setting(key).
cacheTtlnumber60000In-memory cache TTL in milliseconds. Set to 0 to disable caching.
autoSeedbooleantrueWhether to actually seed defaults on init. Set to false to expose SETTING_<key> providers without writing rows.
globalbooleantrueWhether the module is registered as global. Set to false to scope it explicitly.
SettingsModule.forRoot({
  defaults: [
    { key: "app.name", value: "Nestbolt", type: "string" },
    { key: "app.port", value: 3000, type: "number" },
    { key: "mail.host", value: "smtp.example.com", group: "mail" },
  ],
  cacheTtl: 30_000,
});

SettingDefinition

FieldTypeRequiredDescription
keystringyesSetting key. Must be a non-empty string ≤255 chars with no control characters.
valueunknownyesThe default value. Used by autoSeed and as the fallback returned to @Setting(key) consumers when the key is missing from the DB.
type"string" | "number" | "boolean" | "json"noExplicit type. Inferred from value when omitted.
groupstringnoOptional group tag.
descriptionstringnoOptional human-readable description.

How defaults Interacts with autoSeed

autoSeedDatabase side effect on init@Setting(key) injection
true (default)Each missing default is written via INSERT ... OR IGNORE -- existing rows are never overwritten.Always returns the persisted (or seeded) value.
falseNo rows are written.Returns the live DB value if the key exists, otherwise the value from defaults.

The seed batch uses a single INSERT ... OR IGNORE (or its dialect equivalent) so two app instances starting simultaneously won't fight for the same row.

forRootAsync

Use forRootAsync() when you need module setup to depend on other providers (e.g., ConfigService):

import { ConfigModule, ConfigService } from "@nestjs/config";
import { SettingsModule } from "@nestbolt/settings";

@Module({
  imports: [
    ConfigModule.forRoot(),
    SettingsModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        cacheTtl: config.get("settings.cacheTtl", 60_000),
        defaults: [
          { key: "app.name", value: config.get("APP_NAME"), type: "string" },
        ],
      }),
    }),
  ],
})
export class AppModule {}

forRootAsync Options

OptionTypeDescription
importsModuleMetadata["imports"]Modules to import for dependency injection.
injectArray<InjectionToken | OptionalFactoryDependency>Providers to inject into the factory function.
useFactory(...args) => SettingsModuleOptions | Promise<SettingsModuleOptions>Factory function that returns the options.
globalbooleanWhether the module is registered as global (default true).

forRootAsync and Per-Key Providers

Unlike forRoot(), the async form does not create SETTING_<key> providers from defaults -- the keys aren't known until the factory runs, by which point the provider graph is already built. If you need @Setting(key) injection, use the synchronous forRoot() (or supply your own per-key provider in your module).

The async form still seeds defaults on init when autoSeed !== false.

Choosing a cacheTtl

ValueBehaviorWhen to use
60_000 (default)Reads served from cache for 1 minute, then re-fetched.Most apps -- read-heavy with infrequent writes.
0Cache disabled -- every read hits the database.Tests, multi-instance deployments where one instance writes and another reads, or when you can't tolerate a 1-minute lag.
< 60_000Shorter TTL -- more reads but tighter consistency.Settings that change often (feature flags toggled by an admin UI).

The cache lives in-process. set() and forget() invalidate the local cache, but other Node processes won't notice the change until their cached entry expires. For a multi-process setup, either set cacheTtl: 0 or wire up your own cache-invalidation message bus by listening to the events.

Identifier Validation

Every key written or read goes through assertValidKey(), which enforces:

  • non-empty string,
  • length ≤ 255 characters,
  • no control characters (\x00\x1F, \x7F).

The check runs at every public method on SettingsService and at decoration time for @Setting(key), so bad keys fail fast with a clear error.

Reading Options Back

SettingsService.getOptions() returns the resolved SettingsModuleOptions -- handy for tests and for code that needs to behave differently when caching is off:

const options = this.settings.getOptions();
if (options.cacheTtl === 0) { /* ... */ }