@jrh/adapt v6.0.0
@jrh/adapt
Keep your Node.js application's configuration in one place. Access it simply and declaratively. Easily change it based on your application's environment.
Installation
npm install @jrh/adapt
Basic Usage
Setting Up
To get started, all you need is a Configuration File in context/context.json
(relative to the root of your project).
You can then use the Selecting Function to access your configuration data.
For example:
context/context.json
{
"app": {
"name": "hello-world"
}
}
app.js
import adapt from '@jrh/adapt'
const appName = adapt('app.name')
console.log(appName)
CLI
node app.js
> "hello-world"
Adding Adaptive Objects
Maybe your app's configuration values change based on its environment. To handle this, add Adaptive Objects and the mode
environment variable.
context/context.json
{
"app": {
"names": {
"dev": "hello-world-dev",
"production": "hello-world-production"
}
}
}
app.js
import adapt from '@jrh/adapt'
const appName = adapt('app.name')
console.log(appName)
CLI
mode=dev node app.js
> "hello-world-dev"
mode=production node app.js
> "hello-world-production"
Adding Adaptive Values
If there's configuration data you'd rather keep secret, bake in some Adaptive Values.
adapt
will look in the Environment File for these values. If it can't find them there, it will look in process.env
.
This is useful in production environments where the context/environment.json
file may not exist.
Note: Values in the Environment File will take precedence over (but not replace) values in process.env
.
context/context.json
{
"api": {
"token": "[API_TOKEN]"
}
}
context/environment.json
{
"API_TOKEN": "super-secret-token"
}
app.js
import adapt from '@jrh/adapt'
const apiToken = adapt('api.token')
console.log(apiToken)
CLI
node app.js
> "super-secret-token"
Adaptive Values + Adaptive Objects
You can use Adaptive Objects in the Environment File too.
context/context.json
{
"app": {
"names": {
"dev": "hello-world-dev",
"production": "hello-world-production"
}
},
"api": {
"token": "[API_TOKEN]"
}
}
context/environment.json
{
"API_TOKEN": {
"dev": "super-secret-token-dev",
"production": "super-secret-token-production"
}
}
app.js
import adapt from '@jrh/adapt'
const apiToken = adapt('api.token')
console.log(apiToken)
CLI
mode=dev node app.js
> "super-secret-token-dev"
mode=production node app.js
> "super-secret-token-production"
Putting It All Together
With our powers combined, make your application adaptable!
context/context.json
{
"app": {
"name": "hello-world-[APP_NAME]"
},
"api": {
"tokens": {
"dev": "not-secret-token",
"staging": "[API_TOKEN]",
"production": "[API_TOKEN]"
}
},
"services": {
"logging": {
"keys": {
"_default": "default-logging-key",
"staging": "[LOGGING_KEY]",
"production": "[LOGGING_KEY]"
}
}
}
}
context/environment.json
{
"APP_NAME": {
"dev": "from-environment-dev",
"production": "from-environment-production"
},
"API_TOKEN": "super-secret-token",
"LOGGING_KEY": {
"staging": "super-secret-logging-key-staging",
"production": "super-secret-logging-key-production"
}
}
app.js
import adapt from '@jrh/adapt'
const appName = adapt('app.name')
const apiToken = adapt('api.token')
const loggingKey = adapt('services.logging.key')
console.log('App Name:', appName)
console.log('API Token:', apiToken)
console.log('Logging Key:', loggingKey)
CLI
mode=dev node app.js
> "App Name: hello-world-from-environment-dev"
> "API Token: not-secret-token"
> "Logging Key: default-logging-key"
mode=staging node app.js
> "App Name: hello-world-from-environment-staging"
> "API Token: super-secret-token"
> "Logging Key: super-secret-logging-key-staging"
mode=production node app.js
> "App Name: hello-world-from-environment-production"
> "API Token: super-secret-token"
> "Logging Key: super-secret-logging-key-production"
Configuration
The Configuration File
The Configuration File lives at context/context.json
(relative to the root of your project). To change the default location, see Customizing File Locations.
This file can contain Adaptive Objects and Adaptive Values which will be parsed by the Selecting Function.
Example Configuration File
{
"api": {
"keys": {
"_default": "bcd-234",
"staging": "abc-123"
"production": "[API_KEY]"
}
},
"general": {
"number": 5,
"text": "Hello world"
}
}
The Environment File
The Environment File lives at context/environment.json
(relative to the root of your project). To change the default location, see Customizing File Locations.
This file is optional and contains environment variables for your application. It typically should not be committed to source control.
the Environment File file can contain Adaptive Keys which will be parsed by the selecting function.
If the selecting function can't find a value in the Environment File, it will look in process.env
. This is useful in production environments where the context/environment.json
file may not exist.
Note: Values in the Environment File will take precedence over (but not replace) values in process.env
.
Adaptive Values cannot be used in the Environment File.
Example Environment File
{
"API_LOCATION": {
"_default": "dev-api.com",
"staging": "staging-api.com"
"production": "production-api.com"
},
"DATABASE_URL": "mysql://mydb"
}
Customizing File Locations
When importing adapt
, it will look in the context
directory (relative to the root of your project) for the Configuration and Environment files.
To customize where adapt
looks for these files, use the loadAdapt
function instead of the default export:
import { loadAdapt } from '@jrh/adapt'
const adapt = loadAdapt({
configurationDirectory: '/my/custom/directory'
})
Programmatic Configuration
If you want to get even closer to the metal, you can skip file loading altogether and give adapt
the data it needs programatically using the createSelectingFunction
function:
import { createSelectingFunction } from '@jrh/adapt'
const adapt = createSelectingFunction({
configuration: {
// Provide configuration data like in the Configuration File.
},
environment: {
// Provide environment data like in the Environment File.
},
// Optionally define a mode (or use `process.env.mode`).
mode: 'dev'
})
Detailed Usage
The Selecting Function
Usage
import adapt from '@jrh/adapt'
Syntax
adapt(selector)
Arguments
Name | Type | Description | Example |
---|---|---|---|
selector | String | A string in dot notation which targets a key in the configuration file. | api.key |
Exceptions
Throws a standard Error
if:
- The selector is
null
orundefined
. - A matching key or Adaptive Key cannot be found in the Configuration File.
- The value of a selected key is
undefined
.
Normal Keys
Normal keys can be accessed by name, using dot notation.
Using the Example Configuration File:
adapt('api.keys.staging') // 'abc-123'
adapt('general.number') // 5
adapt('general.text') // 'Hello world'
Adaptive Objects
In the Configuration File
In the Configuration File, Adaptive Objects have two special qualities:
- The key which contains them is plural (i.e.
tokens
), - They are an object with keys representing application modes, such as:
"keys": {
"dev": "key-dev",
"staging": "key-staging"
}
Adaptive Objects in the Configuration File will be accessed when the singular form of a given selector (api.key
) cannot be found.
A key from the object will be chosen based on the mode
environment variable.
See Adding Adaptive Objects for an example.
In the Environment File
In the Environment File, Adaptive Objects have one special quality:
- They are an object with keys representing application modes, such as:
"API_TOKEN": {
"dev": "token-dev",
"staging": "token-staging"
}
Adaptive Objects in the Environment File will be accessed when the Configuration File contains an Adaptive Value.
A key from the object will be chosen based on the mode
environment variable.
See Adaptive Values + Adaptive Objects for an example.
The Default Key
All Adaptive Objects can contain a _default
key.
This key will be accessed if the mode
environment variable is not set, or if there are no keys within the Adaptive Object which match the current mode
.
See Putting it All Together for an example.
The mode
Environment Variable
Adaptive Objects can only function if the mode
environment variable is set.
It can be set however you like, as long as it is present when the Selecting Function or the adapt
command are used.
Adaptive Values
Adaptive Values are used in the Configuration File to reference data in the environment.
These values are surrounded in brackets, such as [API_KEY]
. This tells the Selecting Function to replace this section with a value from the Environment File or process.env
.
See Adding Adaptive Values for an example.
Interpolated Adaptive Values
Adaptive Values can contain additional text and/or multiple bracketed sections. For example:
{
"database": "mysql://[DATABASE_USER]:[DATABASE_PASSWORD]"
}
See Putting it All Together for an example.
Preloading Environment Data
By default, adapt
will not load data from the Environment File into process.env
.
If you'd like to do this you can use the adapt
command.
Example Environment Files
Basic
{
"API_KEY": "api-key",
"DATABASE_URL": "db-url"
}
With Adaptive Objects
{
"APP_ID": "abc123",
"API_KEY": {
"_default": "base-key",
"production": "production-key"
},
"DATABASE_URL": {
"dev": "dev-db",
"staging": "staging-db",
"production": "production-db"
}
}
The adapt
Command
The adapt
command will run shell commands with the environment variables defined in your Environment File loaded into process.env
.
This can be useful when running a command line utility which relies on environment variables.
Usage
adapt '[command]'
Example
context/environment.json
{
"DATABASE_URL": "my-database"
}
CLI
mode=testing adapt "echo $DATABASE_URL"
> "my-database"