Brek is a structured, typed config loader for Node.js — ideal for dynamic environments and securely managing secrets like those in AWS Secrets Manager.
Brek stands for Blocking Resolution of Environment Keys.
config/default.json
{
"foo": "bar",
"baz": {
"qux": 42
},
"quux": {
"[awsSecret]": {"key": "demo"}
}
}blah.ts
import {getConfig} from "brek";
const {foo, baz} = getConfig();
console.log(foo); // "bar"
console.log(baz.qux); // 42
console.log(baz.quux); // "secret_demo"
console.log(baz.jazz); // undefined and Typescript will throw an error at compile time
// Enjoy full autocompletion and type safety! 🚀- Turn your runtime errors into safer compile-time errors! Automatically generated Typescript type definition for configuration object
- All settings are in simple, logic free
.jsonfiles. - Adds structure and organization to your configuration files
- Easily see what is being overridden and where
- Comprehensive yet easy to understand documentation
- Small, modular, and unit-tested codebase written in Typescript.
- No dependencies!
- Differentiates between concepts such as
environment,deployment, anduserand provides an out-of-the-box solution with sensible merge strategy - Provides for overrides via CLI without polluting the CLI argument namespace
- Fast. Runtime processing is done once during app initialization only.
- Put environment variables directly into .json files
- Great for AWS Secrets Manager, AWS Parameter Store, HashiCorp Vault, or custom dynamic runtime functions
- Any custom logic lives in loaders, keeping your config files logic-free
- Provides an easy sharable and reusable plugin interface for sharing or re-use
- Getting Started
- Configuration Rules
- Configuration Merge Strategy
- Using CLI/ENV Overrides
- Environment Variables in Config Files
- Loaders
- API Reference
- CLI Reference
- Recommended Best Practices
- Usage with AWS Lambda
- Debugging
- Known Issues
- Support, Feedback, and Contributions
- Why is it called brek?
- Sponsorship
- Other Useful Libraries
To install and get started with brek, see our Getting Started guide.
-
default.jsonis required, everything else is optional. Recommended practice is thatdefault.jsoncontains all of your "local development" settings. -
All configuration files must be a subset of
default.json. Think of them simply as overrides to the default. In Typescript terms, configuration files must be of typePartial<Config>. -
A property's type should not change simply because of a different environment, user, or deployment. This is basically saying the same as above.
-
Loaders always must return a string. If you need to return a different type, you can use
JSON.parse()or similar. -
Arrays should be homogenous (not of mixed types).
Brek uses a simple and intuitive merge strategy, with the goal of making it easy to understand what is being overridden and where. You can specify configuration overrides for different environments, deployments, and users.
Configurations are merged in this order, with the later ones overriding the earlier ones:
- default.json
- environment file
- deployment file
- user file
- CLI/env overrides
Which of these sources to choose depends on the presence of certain process.env configuration variables:
| process.env | conf file |
|---|---|
ENVIRONMENT, NODE_ENV |
/conf/environments/[ENVIRONMENT].json |
DEPLOYMENT |
/conf/deployments/[DEPLOYMENT].json |
USER |
/conf/users/[USER].json |
A few notes:
ENVIRONMENTtakes precedence overNODE_ENVif both are set. This is useful for local development, or to not be tied to theNODE_ENVconvention (ie, if you have a package that usesNODE_ENVfor something else).USERis usually provided by default by UNIX environments (tryconsole.log(process.env.USER))- Arrays and loaders are simply replaced, not merged.
You specify these by setting the appropriate process.env variables. For example, to use the production environment, set NODE_ENV=production or ENVIRONMENT=production.
You can use the BREK environment variable to override properties via CLI/ENV. BREK must be valid JSON. Using jq simplifies dynamic JSON construction, ensures proper quoting, and makes it easier to handle environment variables.
# Override the a.b property
BREK=$(jq -n '{a: {b: "q"}}') ts-node src/index.ts
# Override the postgres property wth an environment variable
DATABASE_URL="postgres://user:pass@localhost:5432/db"
BREK=$(jq -n --arg db "$DATABASE_URL" '{postgres: $db}') ts-node src/index.tsYou can use environment variables as values by wrapping it in ${...}. For example, to use environment variable FOO, use ${FOO}. This will translate to process.env.FOO. These will always be typed as strings. Example config file:
{
"foo": "${FOO}"
}Loaders are custom functions that are called during startup (run-time). This can be used to do anything, such as fetching secrets from AWS Secrets Manager, or any other dynamic runtime operation. They can be Promise/async/await based.
Quick example:
config/default.json
{
"foo": {
"[fetchSecret]": {
"key": "demo"
}
}
}To learn more about loaders, see the Loaders documentation.
Returns the configuration object. Lazy loads the configuration files from disk via loadConfig() if config.json does not exist.
Preloads/generates the configuration. Loads the configuration files from disk, merges them, resolves any loaders, and writes the final configuration to config.json. Same as calling brek load-config from the CLI.
You can call the binary brek to perform various operations. You can use npx brek or in your package.json scripts.
Preloads the configuration. Loads the configuration files from disk, merges them, resolves any loaders, and writes the final configuration to config.json.
Writes the types to config/Config.d.ts unless otherwise specified. This must be called whenever default.json is changed.
default.jsonshould contain all of your local development settings, and then "progressively enhance" from there.- Use AWS Secrets Manager or Hashicorp Vault to store your sensitive information and use a loader to load them.
AWS Lambda has a read-only file system, except for the temporary /tmp directory, which is writable. To handle this, set the environment variable BREK_WRITE_DIR to /tmp.
You may also want to run brek load-config after your build step. Example:
{
"scripts": {
"build": "tsc",
"load-config": "ENVIRONMENT=prod BREK_WRITE_DIR=/tmp brek load-config",
"prepare": "npm run build && npm run load-config"
}
}If the loaders must be executed during runtime, then you can use the await loadConfig() method in your Lambda handler to ensure the configuration is loaded before your function executes. Don't forget to include BREK_WRITE_DIR=/tmp in your Lambda environment variables.
You can set the BREK_DEBUG environment variable to see debug output. Example:
BREK_DEBUG=1 ts-node src/index.tsUse with caution! This may output sensitive information to the console.
-
Some IDEs (particularly IntelliJ/Webstorm) occasionally have some issues with caching of the generated
Config.d.ts file(which is stored in yourconfigfolder). If you run into this problem, restarting your TS service. -
If you're using AWS Lambda, see the Usage with AWS Lambda section.
-
If the iguration ever gets out of date, you'll need to do one or more of the following:
- Call
brek write-typesto regenerate the type declaration file and delete the disk cache (config.json) - Make sure you're calling
brek load-configorloadConfig(): Promise<void>before app startup - Restart your app to clear cache in memory
- Call
- ⭐ Star this repo if you like it!
- 🐛 Open an issue for bugs or suggestions.
- 🤝 Submit a PR to
main— all tests must pass.
Brek stands for Blocking Resolution of Environment Keys.
- autorel: Automate semantic releases based on conventional commits. Similar to semantic-release but much simpler.
- hoare: An easy-to-use, fast, and defensive JS/TS test runner designed to help you to write simple, readable, and maintainable tests.
- jsout: A Syslog-compatible, small, and simple logger for Typescript/Javascript projects.
- cjs-mock: NodeJS module mocking for CJS (CommonJS) modules for unit testing purposes.
- typura: Simple and extensible runtime input validation for TS/JS, written in TS.