Node.js configuration management without config or dotenv
The config
npm package is great (npmjs.com/package/config), but it encourages confusing and non-12-factor-app-compliant patterns.
We’ll look at some of the patterns it encourages and why they’ll bring you struggles down the road as well a simple, single-file, no-dependency way to define your configuration.
Table of Contents
Sprawling configuration: hard to pinpoint where config is set
The main thing it encourages is configuration sprawl: some of your configuration lives in JSON files, some of your configuration comes from environment variables (and is glued together using JSON files). Some config fields change depending on NODE_ENV
, others do not.
Worst of all, config is dynamically loaded using a config.get('path.in.the.json.config.object')
call. This creates an affordance for users to have deeply-nested configuration object(s), which isn’t desirable, your configuration should be minimal and it shouldn’t live in application code.
See the following from the “config” section in “The Twelve-Factor App” (see it in full at 12factor.net/config):
Apps sometimes store config as constants in the code. This is a violation of twelve-factor, which requires strict separation of config from code. Config varies substantially across deploys, code does not.
Non-granular configuration
Here’s another reason why having a package that makes it easy to have config objects isn’t a good idea according to 12 Factor again (see the full config section at 12factor.net/config):
In a twelve-factor app, env vars are granular controls, each fully orthogonal to other env vars. They are never grouped together as “environments”, but instead are independently managed for each deploy.
Having a default.json
, production.json
, test.json
, custom-environment-variables.json
is just not 12-factor, since you’re not supposed to group your config. It should be “here’s a database connection URL”, “here’s a backing service URL”, “here’s a cache connection string”.
It entices developers down the line keep adding switches and settings in a "database": {}
field. Those concerns will not be orthogonal to each other, what’s more, they’re likely to be application-level concerns, eg. “should the database client try to reconnect?”. That’s not something you should be overriding with environment variables or toggling across the environments. It’s a setting that should be hard-coded into the application depending on whether or not the database is critical for example.
A single config.js file
config.js
at the root of you application would look like this:
module.exports = {
NAME_OF_CONFIG: process.env.NAME_OF_CONFIG || 'default-config',
DATABASE_URL: process.env.DATABASE_URL,
REDIS_URL: process.env.REDIS_URL || 'localhost:6379',
X_ENABLED: process.env.X_ENABLED === 'true',
};
In the above, there are examples for how you would default a configuration variable (NAME_OF_CONFIG
, REDIS_URL
) and how you would check a boolean flag (X_ENABLED
).
Making process.env fit for purpose
In Node.js process.env
variables (environment variables) are strings, JavaScript is pretty loose with types, but it’s sometimes useful to convert process.env
variables to another type.
Parsing a number from process.env
const SESSION_TIMEOUT = parseInt(process.env.SESSION_TIMEOUT, 10)
module.exports = {
SESSION_TIMEOUT
};
Converting to Boolean from process.env
Comparing against the 'true'
string tends to be enough:
module.exports = {
IS_DEV: process.env.IS_DEV === 'true',
};
Consuming config.js
To get data from config.js
would be like the following, where we conditionally set some 'json spaces'
, a request timeout and listen on a port with an Express app,
const { REQUEST_TIMEOUT, X_ENABLED, PORT } = require('./config')
const express = require('express')
const app = express()
if(X_ENABLED) {
app.set('json spaces', 2)
}
app.use((req, res, next) => {
req.setTimeout(REQUEST_TIMEOUT);
next()
})
app.listen(PORT, () => {
console.log(`App listening on ${PORT}`);
});
Bonus: getting values from a .env file
Sometimes you’ll want to export values from a .env file into your shell session The following snippet does just this and is an extract of this bash cheatsheet.
export $(cat .env | xargs)
Note the above works for *NIX environments
Photo by Filip Gielda
Get The Jest Handbook (100 pages)
Take your JavaScript testing to the next level by learning the ins and outs of Jest, the top JavaScript testing library.
orJoin 1000s of developers learning about Enterprise-grade Node.js & JavaScript