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
.envfile 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. :)
Top comments (3)
Just found that the declaration files were missing in
v10.0.0. It's fixed inv10.0.1. ๐This looks great! I can't wait to use it in my project. Thank you for sharing your work ๐
Glad that you like it ๐
Feel free to leave a feedback after using it!