NestboltNestbolt

@nestbolt/settings

Decorator

Inject defaulted settings directly into NestJS providers with @Setting(key).

@Setting(key)

@Setting(key) is a parameter decorator that injects the resolved value of a setting that's been pre-registered in SettingsModule.forRoot({ defaults: [...] }).

import { Injectable } from "@nestjs/common";
import { Setting } from "@nestbolt/settings";

@Injectable()
export class GreeterService {
  constructor(@Setting("app.name") private readonly appName: string) {}

  greet() {
    return `Hello from ${this.appName}!`;
  }
}

How It Works

For each entry in defaults, SettingsModule.forRoot() creates a provider with the token SETTING_<key>:

{
  provide: `SETTING_${def.key}`,
  useFactory: async (service: SettingsService) => service.get(def.key, def.value),
  inject: [SettingsService],
}

@Setting(key) is equivalent to @Inject("SETTING_<key>") -- the decorator just builds the token string from the key you pass in. The factory runs once at provider construction time and resolves to the live DB value, falling back to the value from defaults when the key isn't in the database.

Registering the Setting

The key must appear in SettingsModule.forRoot({ defaults: [...] }):

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: "feature.darkMode", value: false, type: "boolean" },
      ],
    }),
  ],
})
export class AppModule {}

Then any provider in any module can inject any of those settings:

@Injectable()
export class HttpServer {
  constructor(@Setting("app.port") private readonly port: number) {}
}

@Injectable()
export class FeatureGate {
  constructor(@Setting("feature.darkMode") private readonly darkMode: boolean) {}
}

Key Validation

The key passed to @Setting() is validated at decoration time -- when the class is loaded, before NestJS even builds the provider graph. Invalid keys throw immediately:

@Setting("");          // throws: key must be a non-empty string
@Setting("bad\nkey");  // throws: key contains control characters
@Setting("a".repeat(300)); // throws: key exceeds 255 characters

This catches typos and bad inputs at startup rather than at the first DB read.

Combining with Other Decorators

@Setting() is just @Inject() under the hood, so it composes with any other parameter decorator:

@Injectable()
export class MyService {
  constructor(
    @Setting("app.name") private readonly appName: string,
    @Inject("SOME_OTHER_TOKEN") private readonly other: SomeService,
    private readonly logger: Logger,
  ) {}
}

Limitations

forRootAsync Doesn't Create Per-Key Providers

The @Setting(key) decorator only works when the key is registered via forRoot({ defaults: [...] }). The asynchronous forRootAsync() doesn't expose per-key providers because the keys aren't known until the factory runs -- by which point the provider graph is already locked.

If you need @Setting(key) injection with async configuration, either:

  1. Use forRoot({ defaults: [...] }) synchronously and read the values lazily inside useFactory for the keys you actually need at module-load time, or
  2. Provide your own SETTING_<key> providers manually in the consuming module.

One-Shot Resolution

The decorator resolves the value once at construction. If the setting changes via service.set() later, already-constructed instances continue to hold the old value -- the injected primitive is captured in the constructor argument.

For values that need to track DB state at request time, inject SettingsService and call service.get(key) instead.

When to Use the Decorator vs. the Service

Use @Setting(key) when...Use SettingsService when...
The key is well-known and registered in defaults.The key is dynamic or chosen at runtime.
You want a clean, typed constructor argument.You need fresh reads on every call (after set() mutations).
The value is read-only from this provider's perspective.You want to call set(), forget(), all(), or group().
Module setup is synchronous.Module setup is async (forRootAsync).