npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

configtree-loader

v0.3.1

Published

Load Spring Boot-style configtree directories into a flat config object

Readme

configtree-loader

Load Spring Boot-style configtree directories into a flat config object for Node.js, Bun, and Deno.

Reads a directory where each filename = config key and file content = config value, returning a flat Record<string, string>. Subdirectories are ignored.

Install

npm install configtree-loader
# or
bun add configtree-loader

Quick Start

Given this directory structure:

/etc/config/db/
  DATABASE_HOST      → "postgres.internal"
  DATABASE_PORT      → "5432"
  DATABASE_USERNAME  → "app_user"
  DATABASE_PASSWORD  → "s3cr3t"
import { loadConfigTree, loadConfigTreeSync } from "configtree-loader";

// Async (recommended)
const config = await loadConfigTree("/etc/config/db");
// {
//   DATABASE_HOST: "postgres.internal",
//   DATABASE_PORT: "5432",
//   DATABASE_USERNAME: "app_user",
//   DATABASE_PASSWORD: "s3cr3t"
// }

// Sync
const config = loadConfigTreeSync("/etc/config/db");

Multiple Directories

Pass an array of paths to merge multiple configtree directories into one object. When the same key exists in multiple directories, the last one wins.

// Mirrors multiple Spring Boot configtree imports:
// spring.config.import:
//   - optional:configtree:/etc/config/db/
//   - optional:configtree:/etc/config/redis/
//   - optional:configtree:/etc/config/overrides/

const config = await loadConfigTree(
  ["/etc/config/db", "/etc/config/redis", "/etc/config/overrides"],
  { optional: true },
);

Spring Boot Equivalent

This library mirrors Spring Boot's configtree feature:

spring:
  config:
    import:
      - optional:configtree:/etc/config/db/
      - optional:configtree:/etc/config/kafka/

The optional: prefix maps to the optional: true option:

// Returns {} instead of throwing if the directory doesn't exist
const config = await loadConfigTree("/etc/config/db", { optional: true });

API

loadConfigTree(dirPath, options?): Promise<ConfigObject>

Asynchronously loads one or more configtree directories. File reads within each directory are parallelized.

loadConfigTreeSync(dirPath, options?): ConfigObject

Synchronously loads one or more configtree directories.

Parameters

| Parameter | Type | Description | | --------- | -------------------- | ------------------------------ | | dirPath | string \| string[] | Path or array of paths to load | | options | LoadOptions | Optional settings (see below) |

Options

| Option | Type | Default | Description | | ---------- | --------- | ------- | -------------------------------------------------------------- | | optional | boolean | false | Return {} instead of throwing when a directory doesn't exist |

Types

type ConfigObject = Record<string, string>;

interface LoadOptions {
  optional?: boolean;
}

Behavior

  • Flat output: All values are strings. Subdirectories are ignored.
  • Whitespace trimming: File contents are trimmed of leading/trailing whitespace and newlines — consistent with how Kubernetes secrets are mounted.
  • Last-writer-wins: When loading an array of paths, later directories overwrite earlier ones for the same key.
  • Empty directories: Returns {}.
  • Symlinks: Symlinks are silently ignored.

Common Use Cases

Kubernetes Secrets

Kubernetes mounts secrets as files in a directory, making this library a natural fit:

const secrets = await loadConfigTree("/var/run/secrets/myapp", { optional: true });
// { DATABASE_PASSWORD: "...", API_KEY: "..." }

Docker Secrets

const secrets = await loadConfigTree("/run/secrets", { optional: true });

Merging Multiple Secret Mounts

const config = await loadConfigTree(["/etc/config/db", "/etc/config/kafka", "/etc/config/redis"], {
  optional: true,
});

config Singleton (v0.3.0+)

configtree-loader ships a ready-to-use config singleton that combines a YAML config file with one or more configtree directories in a single import.

How it works

On import, config reads a YAML file (default: config/app.yaml relative to process.cwd()), merges any configtree directories listed under the configtrees key, and exposes the result as an AppConfig object.

import { config } from "configtree-loader";

console.log(config.configtreeValues); // merged key→value pairs from all configtree dirs
console.log(config.someYamlKey); // any other top-level key from app.yaml

config/app.yaml format

# Arbitrary top-level keys are passed through as-is
environment: production
region: us-east-1

# configtrees is the special key: a list of directories to load
configtrees:
  - /etc/config/db
  - /etc/config/kafka

The directories listed under configtrees are loaded with loadConfigTreeSync and merged into configtreeValues. Later entries overwrite earlier ones for the same key. The configtrees key itself is stripped from the final object.

AppConfig type

interface AppConfig {
  /** Merged key→value pairs loaded from all configured configtree directories. */
  configtreeValues: Record<string, string>;
  /** All other top-level keys from app.yaml. */
  [key: string]: unknown;
}

Default file location and APP_CONFIG_FILE

By default the singleton reads config/app.yaml relative to process.cwd(). Override this at runtime with the APP_CONFIG_FILE environment variable:

APP_CONFIG_FILE=/path/to/custom.yaml node dist/server.js

If the file is missing (ENOENT), config silently returns { configtreeValues: {} } — no exception is thrown.

Side-effect-on-import and test environments

Because config is evaluated at import time, point APP_CONFIG_FILE to a fixture before importing in tests:

// In your test setup or at the top of the test file (before importing config)
process.env["APP_CONFIG_FILE"] = "/path/to/test-config.yaml";

const { config } = await import("configtree-loader");

Or use a minimal fixture file that contains only what your tests need:

# test/fixtures/app.yaml
configtrees: []

License

MIT