A smarter dotenv for Node.js
Tim Wong
Posted on October 18, 2021
If you've been coding in Node.js for some time, it's likely that you've used or at least heard of dotenv.
Dotenv is a zero-dependency module that loads environment variables from a
.env
file intoprocess.env
.
It's one of the must-have libraries which I install in nearly all of my projects, until I published typed-dotenv last year.
Demo
Instead of explaining the difference between dotenv and typed-dotenv, let's feel it by seeing how we write my-api-client.js
differently.
dotenv
/* my-api-client.js */
const { config } = require('dotenv');
const HttpClient = require('./http-client');
config();
const required = ['MY_API_HOST', 'MY_API_KEY'];
for (const key of required) {
if (!process.env[key]) {
throw new Error(`Missing the environment variable "${key}"`);
}
}
const config = {
host: process.env.MY_API_HOST,
apiKey: process.env.MY_API_KEY,
timeout: parseInt(process.env.MY_API_TIMEOUT) || 5000,
keepAlive: process.env.MY_API_KEEP_ALIVE === 'true',
};
module.exports = new HttpClient(config);
This is the common way we use dotenv. The code isn't bad right? But can it be better?
typed-dotenv
/* my-api-client.js */
const { config } = require('typed-dotenv');
const HttpClient = require('./http-client');
const { error, env } = config({ rename: { enabled: true } });
// Errors regarding missing required variables, or other config issues.
if (error) {
throw error;
}
module.exports = new HttpClient(env.myApi);
All in a sudden, the custom validation and data conversion are gone. The code is a lot simpler!
It is basically done for the coding side, but we need one more file - .env.template
. This file is for typed-dotenv to do all the hard work, and more importantly, serves as a documentation for others to overview all env-var in one place.
### .env.template ###
##
# @required {string}
MY_API__HOST=
##
# @required {string}
MY_API__API_KEY=
##
# @optional {number} = 5000
MY_API__TIMEOUT=
##
# @optional {boolean} = false
MY_API__KEEP_ALIVE=
Note that the variable names are using double underscores. This is the magic where typed-dotenv turns the variables into the following structure, so you can supply it to new HttpClient(env.myApi)
directly.
{
"myApi": {
"host": "...",
"apiKey": "...",
"timeout": 5000,
"keepAlive": false
}
}
Summary
By composing the .env.template
file, typed-dotenv can...
- convert the env-vars into the desired types (e.g. number, boolean, json, etc.); and
- validate if the required env-vars are defined; and
- assign default values to the optional env-vars; and
- rename the env-vars to fit your purpose; and
- document the env-vars in one place; and
- ...many more.
If you are interested, please give it a try! Comments are welcome.
GitHub: https://github.com/cytim/nodejs-typed-dotenv
NPM: https://www.npmjs.com/package/typed-dotenv
My Personal Recipe
Last but not least, I found that it's usually helpful to wrap typed-dotenv in a config.js
module.
/* config.js */
const { get } = require('lodash');
const { config } = require('typed-dotenv');
const { error, env } = config({
unknownVariables: 'remove',
rename: { enabled: true },
});
if (error) {
throw error;
}
exports.getConfig = (path) => {
const data = path ? get(env, path) : env;
if (data === undefined) {
throw new Error(`The config path does not exist: ${path}`);
}
return data;
};
Then you can use it like getConfig('path.to.some.config')
.
Hope you like it. :)
Posted on October 18, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.