Skip to content

A modern, simplified declarative configuration manager for NodeJS applications

License

Notifications You must be signed in to change notification settings

Incanta/node-config

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

108 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@incanta/config

A hierarchical, folder-based configuration library for Node.js with environment inheritance, variable cross-referencing, kebab-to-camelCase conversion, secrets provider integration, and support for YAML, JSON, JSONC, and JSON5 file formats.

Installation

npm install @incanta/config
# or
yarn add @incanta/config

Quick Start

import config from "@incanta/config";

const port = config.get<number>("server.port");
const debug = config.get<boolean>("environment.development");

By default, @incanta/config looks for a config directory in the current working directory and loads the default environment. You can override this with the NODE_CONFIG_DIR and NODE_CONFIG_ENV environment variables, or by creating a new Config instance directly.

Core Concepts

Directory-Based Configuration

Configuration values are defined by the folder structure, file names, and object keys within those files. Each subfolder and each file (minus its extension) adds a segment to the config key path.

Given this structure:

config/
  default/
    _index.yaml
    server.yaml
    persistence/
      database.yaml
      redis.yaml
  • _index.yaml contents are scoped to the parent folder (no path segment added)
  • server.yaml values are accessed under server.*
  • persistence/database.yaml values are accessed under persistence.database.*
  • persistence/redis.yaml values are accessed under persistence.redis.*

For example, if server.yaml contains:

port: 3000
host: "localhost"

You access these values with:

config.get<number>("server.port");    // 3000
config.get<string>("server.host");    // "localhost"

The _index.yaml / index.yaml File

Index files are special — they do not add a path segment. Their contents are scoped to the parent directory. For example, config/default/_index.yaml:

environment:
  development: true

This is accessed as environment.development, not _index.environment.development. The underscore prefix is optional but recommended to keep the file at the top of file explorers.

Supported File Formats

  • YAML (.yaml, .yml)
  • JSON (.json)
  • JSONC (.jsonc) — JSON with comments
  • JSON5 (.json5)

You can mix file formats within the same config directory.

Environments

Environments let you override default values for different deployment targets (e.g. staging, production). Only values that differ from the default need to be specified in the environment folder.

config/
  default/          # base values (always loaded first)
    server.yaml
  staging/          # overrides for staging
    server.yaml
  production/       # overrides for production
    server.yaml

Set the environment via:

  • Environment variable: NODE_CONFIG_ENV=production
  • Constructor option: new Config({ configEnv: "production" })
  • config.init() call: config.init({ configEnv: "production" })
  • Using config-env: config-env --env=production <command>

When an environment is active, its values are deep merged on top of default. Arrays are replaced entirely (not merged).

Environment Inheritance

Environments inherit from default automatically. For more complex setups, you can inherit from additional environments using a _config.json file inside the environment folder:

config/
  default/
  staging/
    _config.json    # { "parentNames": ["kubernetes"] }
  kubernetes/
  production/
    _config.json    # { "parentNames": ["staging"] }

production/_config.json:

{
  "parentNames": ["staging"]
}

This creates an inheritance chain: default → kubernetes → staging → production. Values are loaded and merged in that order, with later environments taking precedence.

You can specify multiple parents for complex inheritance:

{
  "parentNames": ["production", "cloud-aws"]
}

Inheritance is resolved in order: default → production (and its parents) → cloud-aws → current environment.

Override File

An override.json file placed directly in the config directory root (not inside any environment folder) is loaded last and merges on top of everything:

config/
  default/
  production/
  override.json     # applied last, on top of the active environment

Variable Cross-Referencing

Config values can reference other config values using ${path.to.variable} syntax:

# docker.yaml
image-prefix: "myregistry/myapp"

# server.yaml
image: "${docker.imagePrefix}-server"

config.get<string>("server.image") resolves to "myregistry/myapp-server".

Relative References

You can use relative paths with a leading dot (.):

# realm/instances/default.yaml
key: "default"
namespace: "realm-${./key}"    # resolves to "realm-default"

Relative paths are resolved from the current object's location in the config tree.

Recursive Resolution

References are resolved recursively, so a reference can point to another value that itself contains a reference.

Kebab-Case to camelCase Conversion

By default, kebab-case keys are automatically converted to camelCase. Both forms can be used to access the value:

# server.yaml
max-connections: 100
debug-port: 9229
config.get<number>("server.maxConnections");  // 100
config.get<number>("server.max-connections"); // 100

Variable Casing Options

You can control this behavior with a variableCasing setting in _config.json (or as a key in any config object):

Value Behavior
"camel" Only camelCase keys are available (default)
"original" Only original keys are preserved
"both" Both the original and camelCase keys are available
{
  "variableCasing": "original"
}

Config Base (Template Objects)

You can define a base/template object that other sibling objects inherit from using incantaConfigBase:

# instances/_index.yaml
incantaConfigBase: "base"
# instances/base.yaml
foo: "bar"
hello: "world"
num: 42
# instances/custom.yaml
hello: "city"

The custom object will inherit values from base and override only the specified keys:

config.get<string>("instances.custom.foo");   // "bar" (inherited)
config.get<string>("instances.custom.hello"); // "city" (overridden)
config.get<number>("instances.custom.num");   // 42 (inherited)

Secrets

@incanta/config has built-in support for fetching secrets from external providers. Any config value prefixed with secret| is treated as a secret reference:

password: "secret|my-secret-key"

Supported Providers

Provider Config Value
HashiCorp Vault "vault"
AWS Secrets Manager "aws-secrets-manager"
Azure Key Vault "azure-key-vault"
GCP Secret Manager "gcp-secret-manager"
Local (file-based) "local"
None (disabled) "none"

Configure the provider in your config files:

# secrets.yaml
provider: "none"
cache-duration-seconds: 300

Fetching Secrets

Use getWithSecrets() to resolve secret references:

const dbConfig = await config.getWithSecrets<IDatabaseConfig>(
  "persistence.database"
);

This recursively walks the returned object and replaces any "secret|..." strings with values fetched from the configured secrets provider. Results are cached according to secrets.cache-duration-seconds.

Environment Variable Injection (config-env)

The package includes a config-env CLI tool that injects config values as environment variables into a child process. This tool will also inject the NODE_CONFIG_ENV variable to the child process.

Define an environment.yaml (or environment.json, etc.) file in the config directory root mapping environment variable names to config paths:

# environment.yaml
DATABASE_HOST: "persistence.database.host"
DATABASE_PORT: "persistence.database.port"

CLI Usage

npx config-env <command>

# With a specific config environment:
npx config-env --env=production <command>
npx config-env -e=production <command>

Programmatic Usage

import config from "@incanta/config";

const envVars = config.getConfiguredEnv();
// { DATABASE_HOST: "localhost", DATABASE_PORT: "5432" }

config-settings.json

Place a config-settings.json file in the working directory root to customize the config system's behavior:

{
  "defaults": {
    "dir": "config/node",       // default config directory (default: "config")
    "env": "my-default-env"     // default environment name (default: "default")
  },
  "extraDirs": [
    "/path/to/additional/config"  // extra directories to search for environments
  ]
}

Extra Directories

extraDirs allows environment folders to live outside the main config directory. This is useful when you want to keep project-specific configuration separate from shared configuration:

{
  "extraDirs": [
    "C:\\path\\to\\MyProject\\Config\\Backend"
  ]
}

When resolving an environment name, the library first checks the main config directory, then checks each extra directory in order.

API Reference

Default Export (Singleton)

import config from "@incanta/config";

Returns a singleton Config instance, initialized with defaults on first access.

Config Class

import Config from "@incanta/config/config";

Constructor

const config = new Config(options?: IConfigOptions);
Option Type Description
configDir string Path to the config directory
configEnv string Environment name to load
cwd string Working directory for resolving relative paths

Methods

Method Returns Description
init(options?) void Re-initialize with new options
get<T>(key) T Get a value by dot-separated path; throws if not found
tryGet<T>(key) T | null Get a value, returning null if the key doesn't exist
getWithSecrets<T>(key) Promise<T> Get a value and resolve any secret|... references
processSecrets<T>(value) Promise<T> Recursively resolve secrets in an arbitrary value
getConfiguredEnv() object Get env vars mapped from the environment.* config file
getJson() object Get the entire resolved config as a plain object
set<T>(key, value) void Set a runtime config value
dir() string Get the active config directory path
env() string Get the active environment name
cwd() string Get the working directory

Environment Variables

Variable Description
NODE_CONFIG_DIR Override the config directory path
NODE_CONFIG_ENV Override the config environment name
NODE_CONFIG_SKIP_ENV_WARNING Set to "true" to suppress missing environment warnings

Example Project Structure

A typical project using @incanta/config:

my-project/
  config-settings.json           # optional: customize defaults and extra dirs
  config/
    environment.yaml             # env var mappings for config-env CLI
    override.json                # optional: final overrides
    default/                     # base configuration
      _index.yaml
      server.yaml
      persistence/
        database.yaml
        redis.yaml
    staging/                     # staging overrides
      _config.json               # { "parentNames": ["kubernetes"] }
      _index.yaml
    kubernetes/                  # kubernetes-specific values
      server.yaml
    production/                  # production overrides
      _config.json               # { "parentNames": ["staging"] }
      persistence/
        database.yaml
  src/
    index.ts

License

MIT

About

A modern, simplified declarative configuration manager for NodeJS applications

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •