Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Node.js Bindings

dynamic-config-node on npm pairs this engine with the schema you already write: Rust resolves, your schema validates, JavaScript reads a cached object.

npm install dynamic-config-node
import { DynamicConfig, zodValidator } from "dynamic-config-node"
import { z } from "zod"

const Database = z.object({ host: z.string(), port: z.number().default(5432) })

const db = await new DynamicConfig({ key: "db", validate: zodValidator(Database) })
  .file("config.toml")
  .env("APP_")
  .initAndCurrent()
//    ^? { host: string; port: number }

One prebuilt binary per platform, through Node-API — which is ABI-stable, so the same binary serves Node 18, 20, 22 and whatever comes next. Nothing compiles at install time and nothing but the engine is a dependency.

What a schema is here

A function. It takes the resolved document and answers the value your program reads, or throws. That is the whole contract, and it is why no schema library is a dependency of this package:

You useYou write
Zodvalidate: zodValidator(Schema)
Ajv / TypeBoxvalidate: ajvValidator(compiled)
Neithervalidate: (document) => { … } — a function of your own
Nothingomit validate; the document is the value, read by dotted path

Schemas has each of them, and what changes between them.

What the engine does

Everything the Rust crate does with sources, unchanged: files merge in call order, the environment beats them, .env sits just below the real environment, a secrets directory beats a remote store, profiles select, discovery searches a path, and two runtime layers bracket the rest. Sources & Precedence is the chapter; the order is the same in all three languages.

await config
  .setDefault("pool.maxSize", 8)     // a fallback the program computes
  .discover("app", ["/etc/app", "."])
  .file("config.toml")
  .file("secrets.toml")              // merges over the first, key by key
  .secretsDir("/run/secrets")        // a Docker or Kubernetes mount
  .envFile(".env")
  .env("APP_")
  .init()

Reading is a property read

current() returns a cached object. Validation runs once per successful resolve, never per read, so reading configuration on every request costs what reading a field costs — which is what makes read it per request the advice rather than copy it at boot.

app.get("/", (request, response) => {
  const { rateLimit } = config.current()   // always the document in force
  …
})

The property the design is for

A document the schema refuses installs nothing. A file edited into something invalid leaves the previous document serving and reports the failure — from the watcher exactly as from an explicit reload(). That is what makes it safe to leave a watcher running in production, and it is the first thing Watching & Hooks demonstrates.

Where the parts live

Every method, every argumentAPI Reference
Zod, Ajv, plain functions, no schemaSchemas
The watcher, onReload, onChangeWatching & Hooks
Express, Fastify, NestJS, Next.js, ReactWeb Frameworks
A store written in JavaScript, and the eight Rust onesRemote Stores
What crosses the boundary, and how oftenImplementation Details
What it will not do, and whyLimitations

The engine's own behaviour — precedence, profiles, discovery, the last-known-good cache, encryption, the document shape rules — is the Rust book, because it is the same engine and describing it twice is how two descriptions drift.