NestboltNestbolt

@nestbolt/settings

Seeding

Pre-populate the settings table at startup with defaults and INSERT ... OR IGNORE.

Why Seed?

When a new app instance boots against an empty (or partially populated) database, you want known keys -- app.name, app.maintenance, mail.host -- to exist with sensible defaults so the rest of the application can read them safely. defaults lets you express that without writing a custom migration.

Declaring Defaults

Pass an array of SettingDefinition objects to SettingsModule.forRoot():

import { Module } from "@nestjs/common";
import { SettingsModule } from "@nestbolt/settings";

@Module({
  imports: [
    SettingsModule.forRoot({
      defaults: [
        { key: "app.name", value: "Nestbolt", type: "string" },
        { key: "app.port", value: 3000, type: "number" },
        { key: "app.maintenance", value: false, type: "boolean" },
        { key: "mail.host", value: "smtp.example.com", group: "mail" },
        { key: "mail.port", value: 587, type: "number", group: "mail" },
      ],
    }),
  ],
})
export class AppModule {}

Each definition supports:

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

How Seeding Runs

When autoSeed !== false (the default), onModuleInit() runs a single bulk insert:

await repo
  .createQueryBuilder()
  .insert()
  .into(SettingEntity)
  .values(rows)
  .orIgnore()
  .execute();

Translated by TypeORM to your dialect's "insert and skip duplicates" form -- INSERT ... ON CONFLICT DO NOTHING on Postgres / SQLite, INSERT IGNORE on MySQL.

This means:

  • Existing rows are never overwritten. Operators can change a setting in production without the next deploy resetting it.
  • The seed is race-safe. Two app instances starting simultaneously won't fight for the same row -- the first commit wins, the second is silently ignored.
  • The seed is one round-trip. All defaults are inserted in a single statement, regardless of how many entries are in the array.

Disabling the Seed

Set autoSeed: false to skip the database write while still exposing SETTING_<key> providers:

SettingsModule.forRoot({
  autoSeed: false,
  defaults: [
    { key: "app.name", value: "Nestbolt", type: "string" },
  ],
});

With autoSeed: false:

  • Nothing is written to the database at module init.
  • @Setting(key) still works: it calls service.get(key, def.value) at provider construction, returning the live DB value if it exists or the default otherwise.

This is useful when you want @Setting(key) for typed constructor injection but you maintain settings rows yourself via migrations or an admin UI.

Re-Seeding Behavior

The seed always uses INSERT ... OR IGNORE. Calling it twice (e.g., bouncing the process) writes nothing the second time -- existing rows are preserved as-is.

If you need to reset a setting back to its default, do it explicitly:

await this.settings.forget("app.name");
// Next read will return undefined or the @Setting() default
// Or, to write the default explicitly:
await this.settings.set("app.name", "Nestbolt", { type: "string" });

Seeding with forRootAsync

forRootAsync() honors defaults and autoSeed the same way:

SettingsModule.forRootAsync({
  imports: [ConfigModule],
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    defaults: [
      { key: "app.name", value: config.get("APP_NAME", "Nestbolt") },
    ],
  }),
});

The one difference: the async form does not create per-key SETTING_<key> providers (the keys aren't known until the factory runs). If you need @Setting(key) injection, use the synchronous forRoot() instead. See Decorator -- Limitations for the full rationale.

Validation at Seed Time

Every key in defaults is validated up front before any DB work. A bad key fails fast at module init with a clear error:

SettingsModule.forRoot({
  defaults: [
    { key: "", value: "x" }, // throws: key must be a non-empty string
  ],
});