1.0.94 • Published 5 months ago

hocon-config v1.0.94

Weekly downloads
-
License
MIT
Repository
github
Last release
5 months ago

hocon-config

CI Tests npm version License npm downloads

A powerful HOCON (Human-Optimized Config Object Notation) parser and loader for Node.js. We fully handle:

  • Environment variable substitutions (?ENV_VAR / ${?ENV_VAR})
  • Multiple-file includes (include "overrides.conf")
  • Nested objects & arrays (dotted keys → nested objects)
  • Key merging (last definition wins, partial array overrides, etc.)
  • Programmatic overrides for advanced usage
  • Built-in CLI + ENV merging in the parse function
  • Zero dependencies – only Node’s built-ins
  • Jest-based tests ensuring quality

No need to be humble: hocon-config is robust yet straightforward, making your Node.js configuration a breeze.


Installation

npm install hocon-config

Once installed, you can simply import or require it in your Node.js code.


Quick Start

  1. Create a HOCON file (e.g., config/base.conf):

    app.name = "ExampleApp"
    database {
      host = "localhost"
      port = 5432
    }
  2. Use the parse function to load it, automatically merging environment variables & CLI arguments:

    const path = require('path');
    const { parse } = require('hocon-config');
    
    // Suppose we want to read config/base.conf
    // We'll pass no options => parseEnv=true, parseArgs=true, envPrefix=''
    const filePath = path.join(__dirname, 'config', 'base.conf');
    const config = parse(filePath);
    
    console.log(config);
    // Might print:
    // {
    //   app: { name: 'ExampleApp' },
    //   database: { host: 'localhost', port: 5432 }
    // }
  3. Done! You have a Node.js object ready to use, and if you define environment variables or pass CLI arguments (prefixed keys, etc.), they override the config automatically.


The parse Function

/**
 * parse(filePath, [runtimeOptions]):
 * 1) Gather overrides from process.env + process.argv
 * 2) parseFile(...) with those overrides
 *
 * runtimeOptions:
 *   envPrefix?: string  (default "")
 *   parseEnv?: boolean  (default true)
 *   parseArgs?: boolean (default true)
 *   debug?: boolean
 */
function parse(filePath, runtimeOptions = {}) {
  const {
    envPrefix = "",
    parseEnv = true,
    parseArgs = true,
    debug = false,
  } = runtimeOptions;

  // Collect env-based overrides (keys with prefix => dottedKey)
  let envMap = {};
  if (parseEnv) envMap = buildEnvMap(process.env, envPrefix);

  // Collect CLI-based overrides (--app.name=Override)
  let argMap = {};
  if (parseArgs) {
    const argv = process.argv.slice(2);
    argMap = buildArgMap(argv);
  }

  // CLI overrides env if there's a conflict
  const finalOverrides = { ...envMap, ...argMap };

  // parseFile with final overrides
  return parseFile(filePath, {
    debug,
    overrides: finalOverrides,
  });
}

By default:

  • envPrefix="" ensures only environment variables like app_name'app.name' are considered.
  • parseEnv=true merges environment variables.
  • parseArgs=true merges CLI arguments of the form --some.dotted.key=value.
  • The final config merges these on top of your HOCON file’s contents.

Usage & Examples

Below are 11 scenarios from simple key-values to complex multi-file merges, environment usage, CLI arguments with nested keys, etc.

Scenario 1: Simple Key-Value

config/s1.conf:

hello = "world"

Code:

const conf = parse('config/s1.conf');
console.log(conf);
// => { hello: 'world' }

Nothing fancy.


Scenario 2: Nested Object

config/s2.conf:

app {
  name = "Scenario2"
  nested {
    level = "deep"
  }
}

Code:

const conf = parse('config/s2.conf');
console.log(conf);
// => {
//   app: {
//     name: 'Scenario2',
//     nested: { level: 'deep' }
//   }
// }

Objects like app.nested.level become nested JS objects.


Scenario 3: Arrays & Merging

config/s3.conf:

server.ports = [8080, 9090]
server.ports = [10000]

Code:

const conf = parse('config/s3.conf');
console.log(conf);
// => { server: { ports: [ '10000' ] } }

The second line overwrote the entire array—last definition wins.


Scenario 4: Environment Variables

config/s4.conf:

feature.flag = false
feature.flag = ${?FEATURE_FLAG}

Code:

process.env.FEATURE_FLAG = 'true';
const conf = parse('config/s4.conf');
console.log(conf);
// => { feature: { flag: 'true' } }

Scenario 5: Partial Array Overwrites

config/s5-base.conf:

server.ports = [8080, 9090, 10000]

config/s5-override.conf:

include "s5-base.conf"
server.ports = [${?APP_PORT}]

Usage:

delete process.env.APP_PORT;
const conf1 = parse('config/s5-override.conf');
console.log(conf1.server.ports);
// => [ '8080', '9090', '10000' ] (unchanged)

process.env.APP_PORT = '9999';
const conf2 = parse('config/s5-override.conf');
console.log(conf2.server.ports);
// => [ '9999', '9090', '10000' ]

If $APP_PORT isn’t defined, we skip overwriting the array.


Scenario 6: Simple CLI Override

config/s6.conf:

app.name = "BaseCLI"
server.port = 3000

CLI:

node index.js --app.name=MyCLIoverride --server.port=9999

Code (index.js):

const conf = parse('config/s6.conf');
// => merges env vars w/ prefix '' plus CLI
console.log(conf);
// => { app: { name: 'MyCLIoverride' }, server: { port: '9999' } }

parse sees --app.name=MyCLIoverride{'app.name': 'MyCLIoverride'}, overshadowing file definitions.


Scenario 7: Multi-file Includes

config/s7-base.conf:

app {
  name = "Scenario7"
}
include "s7-mid.conf"

config/s7-mid.conf:

app.midKey = true
include "s7-leaf.conf"

config/s7-leaf.conf:

app.final = "leaf"

Code:

const conf = parse('config/s7-base.conf');
console.log(conf);
// => {
//   app: {
//     name: 'Scenario7',
//     midKey: 'true',
//     final: 'leaf'
//   }
// }

All merges happen in correct order.


Scenario 8: Programmatic Overrides

process.env.FEATURE_FLAG = 'false';
const conf = parse('config/s8.conf', {
  // override everything, if we want
  overrides: {
    'database.host': 'prod-db.internal',
    'app.enableBeta': true
  }
});
console.log(conf);
// merges file + env + CLI + final overrides

Any overrides object merges last, overshadowing everything else.


Scenario 9: Combining Env + CLI + Hard Overrides

config/s9.conf:

app.name = "BaseEnvCLI"
app.debug = false

CLI:

node index.js --app.debug=true

Code:

process.env.app_name = 'EnvOverride'; 
const conf = parse('config/s9.conf', {
  overrides: { 'app.logLevel': 'VERBOSE' }
});
// Priority order (lowest -> highest):
// 1) file  => app.name=BaseEnvCLI, app.debug=false
// 2) env   => app.name=EnvOverride
// 3) CLI   => app.debug=true
// 4) overrides => app.logLevel=VERBOSE
console.log(conf);
// => {
//   app: {
//     name: 'EnvOverride',
//     debug: 'true',
//     logLevel: 'VERBOSE'
//   }
// }

Scenario 10: Parsing a String Instead of a File

const { parseString } = require('hocon-config');

const hoconData = `
  server { port = 3000 }
  feature.enabled = ${'?FEATURE_FLAG'}
`;

process.env.FEATURE_FLAG = 'true';
const inlineConfig = parseString(hoconData, __dirname, { debug: true });
console.log(inlineConfig);
// => { server: { port: '3000' }, feature: { enabled: 'true' } }

No file needed, just inline usage.


Scenario 11: CLI Nested Dotted Keys

config/s11.conf:

app {
  nestedKey = "original"
}

CLI:

node index.js --app.nestedKey=CLIOverride

Code:

// parse() sees envPrefix '' for env, and parseArgs for CLI
const conf = parse('config/s11.conf');
console.log(conf);
// => { app: { nestedKey: 'CLIOverride' } }

parse automatically reads process.argv--app.nestedKey=CLIOverride => {'app.nestedKey': 'CLIOverride'} overshadowing the file definition.


parseFile and parseString

  • parseFile(filePath, options?):
    Loads from a HOCON file, merges environment expansions, partial array merges, includes, plus optional overrides. Typically used behind the scenes by the simpler parse(filePath, runtimeOptions).
  • parseString(hocon, baseDir, options?):
    Same logic, just from inline text. Great for dynamic or test configs.

The star of the show is parse(filePath, runtimeOptions?), which merges environment & CLI arguments automatically so your config can be manipulated by external factors with zero extra code.


License

MIT

1.0.94

5 months ago

1.0.93

5 months ago

1.0.92

5 months ago

1.0.9

5 months ago

1.0.8

5 months ago

1.0.7

5 months ago

1.0.6

5 months ago

1.0.5

5 months ago

1.0.4

5 months ago

1.0.3

5 months ago

1.0.2

5 months ago

1.0.1

5 months ago

1.0.0

5 months ago