Skip to main content
nestrs’ configuration layer lives inside the nestrs::config module and is the in-tree analogue of @nestjs/config. Sources merge in declaration order (later wins); null / empty values delete the previous key; YAML, TOML, and JSON files are all converted to a JSON tree before the typed decode runs, so the typed-access path only ever sees one shape.

Define a typed, namespaced config

ConfigNamespace declares the prefix used both for env-var matching (NESTRS_DB__HOST) and for the dotted-key view inside config files (db.host). One config struct per concern — db, redis, auth, feature_flags. The #[derive(Validate)] runs on boot, so an invalid config panics at startup rather than mid-request.

Register and load

ConfigModule::for_root reads from the process environment (with a dotenvy cascade if a .env file is present at startup), parses each entry against its namespace, runs validation, and exports a ConfigService into the DI container. Boot-time panic on invalid config — match the @nestjs/config derive’s panic-on-load behavior.

Read from a provider

get_by_key::<T>("db") looks up the namespace, decodes the merged overlay, and returns the typed value. The string "db" matches DatabaseConfig::NAMESPACE, so the call site reads like a logical section rather than an env-var path.

Layer file sources

Sources are merged in declaration order:
  1. config/app.json — committed defaults, parsed at compile-time shape.
  2. config/local.json — developer overrides. OptionalFile silently skips a missing path (matches @nestjs/config’s ignoreEnvFile semantics).
  3. Env { prefix: None } — the process environment, always last so deployments can override without rebuilding.
A key set to null in any source deletes the previous value — the typed decode then either errors or fills with the struct’s default.

Raw dotted-key view

When the typed path is too narrow — feature flags with dynamic keys, templating, debugging — pull the merged overlay directly:
snapshot() is the resolved view with namespace re-keying (db.hostNESTRS_DB__HOST), the same shape env-var lookups see. For the pre-rekey view, read service.raw (also a HashMap<String, String>).

File formats

FileFormat is {Json, Toml, Yaml}. Use FileFormat::from_path to pick from the file extension when you don’t want to be explicit:
TOML/YAML scalars are coerced through the same rules as JSON literals: "true" becomes Bool(true), "8080" becomes Number(8080), "" / "null" becomes Null. Numeric / bool decoding only succeeds when the entire string round-trips, so get_by_key::<u16>("db.port") never silently truncates.

Hot reload

With the config-hot-reload feature on the nestrs crate, attach a ConfigWatcher after building the initial ConfigService:
The watcher uses notify to subscribe to fs events on every File / OptionalFile source, debounces bursts, and rebuilds the service on a blocking thread (the decode doesn’t need the tokio runtime). Subsequent reads from the injected ConfigService see the new values. Env and Inline sources are not watched (they aren’t filesystem-resident), so a config drift via env var won’t trigger a reload — by design.

When to use what

  • Typed Config::register<T>() — every production config. Decode
    • validate at boot, get compile errors on rename, and get_by_key reads like a logical section.
  • ConfigService::snapshot() — debugging, dynamic feature flags, or logging the resolved overlay at startup. Read-only.
  • ConfigWatcher — dev loops where config files change frequently and a restart is friction. Don’t ship it behind a prod feature flag — the watcher adds fs syscall overhead per reload, and production reload is usually a deploy event, not a runtime concern.

See also