We recently began using the NPM module config in our Node.js projects. It's a really nice way to enable our clients to configure their software once we deliver it. One of the nicest features, IMHO, is its facility that enables you to define custom environment variables for any given config
setting.
For example, if your configuration file config/default.json
looks like
1 2 3 4 5 6 |
{ "dirs": { "upload": "/tmp", "extracted": "/tmp" } } |
then you can create another file called config/custom-environment-variables.json
like
1 2 3 4 5 6 |
{ "dirs": { "upload": "MYAPP_DIRS_UPLOAD", "extracted": "MYAPP_DIRS_EXTRACTED" } } |
that allows an administrator to override config values in .json files with environment variables. For example, if he sets MYAPP_DIRS_UPLOAD
to /var/uploads
, then config
will return /var/uploads
instead of /tmp
. Nice, right?
However, for large projects or for lazy developers like me, it's easy to forget to add an entry in config/custom-environment-variables.json
whenever you add an entry to config/default.json
(or any other config file).
You see, I really hate grunt work. Since I espouse the invaluable trait of laziness, I created & published an NPM module called config-cev-generator
that takes care of creating the config/custom-environment-variables.json
file for me. BTW, "cev" stands for "custom environment variable", which you probably already noticed. Now, you can have this generated for you! Just
1 |
$ npm install -g config-cev-generator |
and you're ready to go.
For example, in a Node.js project that uses config
, open a command line in the root of the project and try the command
1 |
$ cev |
You'll see all of your config variables ready for use as environment variables printed to stdout. To save it, just redirect the output to config/custom-environment-variables.json
. For example, given the config file above, cev
generates
1 2 3 4 5 6 7 |
$ cev { "dirs": { "upload": "NODE_APP_DIRS_UPLOAD", "extracted": "NODE_APP_DIRS_EXTRACTED" } } |
The default prefix is NODE_APP
, separator is _
, and environment variables (being environment variables) are upper cased by default. But wait! There's more! cev
is nicely configurable:
1 2 3 4 5 6 7 8 9 |
$ cev --help USAGE: node cev [OPTION1] [OPTION2]... arg1 arg2... The following options are supported: -s, --separator Separator. ("_" by default) -p, --prefix Prefix. ("NODE_APP" by default) -c, --casing Casing: "upper", "lower", or "unchanged". ("upper" by default) -f, --pretty Format prettily with given number of spaces for indentation. ("2" by default) -e, --empties If present, preserves sections that wouldn't have any environment variables. Functions are always skipped. -v, --verbose Be verbose. |
Invoking
1 |
$ cev --prefix MYAPP --casing lower |
yields
1 2 3 4 5 6 |
{ "dirs": { "upload": "myapp_dirs_upload", "extracted": "myapp_dirs_extracted" } } |
For small projects, who cares, unless your really lazy, right? But, for large projects, this can become truly annoying. Consider this config/default.json
file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
{ "dirs": { "upload": "", "extracted": "", "regex": "", "format": "" }, "tz": "", "webserver": { "name": "DEFAULT", "port": 4879, "pfx": "cert/cert.pfx", "passphrase": "", "public": "cert/public.crt.pem", "ca": "cert/root-ca.crt.pem", "cakey": "cert/root-ca.key.pem" }, "encryption": { "public": { "keyfile": "keys/public.pem", "key": "" } }, "email": { "username": "", "password": "", "from": "", "host": "smtp.google.com", "port": 25 }, "ios": { "root": "ios", "plist": "MyApp.plist.ejs", "ipa": "MyApp.ipa", "bundleIdentifier": "0000000000.com.scispike.MyApp", "subtitle": "SciSpike MyApp", "title": "SciSpikeMyApp" } } |
Writing the config/custom-environment-variables.json
for that would just, well, suck. Check this out:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
$ cev --prefix MYAPP { "dirs": { "upload": "MYAPP_DIRS_UPLOAD", "extracted": "MYAPP_DIRS_EXTRACTED", "regex": "MYAPP_DIRS_REGEX", "format": "MYAPP_DIRS_FORMAT" }, "tz": "MYAPP_TZ", "webserver": { "name": "MYAPP_WEBSERVER_NAME", "port": "MYAPP_WEBSERVER_PORT", "pfx": "MYAPP_WEBSERVER_PFX", "passphrase": "MYAPP_WEBSERVER_PASSPHRASE", "public": "MYAPP_WEBSERVER_PUBLIC", "ca": "MYAPP_WEBSERVER_CA", "cakey": "MYAPP_WEBSERVER_CAKEY" }, "encryption": { "public": { "keyfile": "MYAPP_ENCRYPTION_PUBLIC_KEYFILE", "key": "MYAPP_ENCRYPTION_PUBLIC_KEY" } }, "email": { "username": "MYAPP_EMAIL_USERNAME", "password": "MYAPP_EMAIL_PASSWORD", "from": "MYAPP_EMAIL_FROM", "host": "MYAPP_EMAIL_HOST", "port": "MYAPP_EMAIL_PORT" }, "ios": { "root": "MYAPP_IOS_ROOT", "plist": "MYAPP_IOS_PLIST", "ipa": "MYAPP_IOS_IPA", "bundleIdentifier": "MYAPP_IOS_BUNDLEIDENTIFIER", "subtitle": "MYAPP_IOS_SUBTITLE", "title": "MYAPP_IOS_TITLE" } } |
Nice, huh?
Here's something that might get you bonus points amongst your fellow geeks (read: lazy programmers). Add config-cev-generator
to your package.json
's devDependencies
, then invoke the project-local cev
command before anything interesting. For example, consider this package.json
fragment:
1 2 3 4 5 6 7 8 9 10 11 12 |
... "devDependencies": { "config-cev-generator": "^0.1.4" }, "scripts": { "cev": "$(npm bin)/cev --prefix COOLNESS > config/custom-environment-variables.json", "pretest": "npm run cev", "prestart": "npm run cev", "prerestart": "npm run cev", ... }, ... |
Now, whenever you run a test or your app, you're sure not to forget to keep your config/custom-environment-variables.json
in sync with the rest of your config! Check this out:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
$ npm install npm WARN package.json cev-test@1.0.0 No description npm WARN package.json cev-test@1.0.0 No repository field. npm WARN package.json cev-test@1.0.0 No README data config-cev-generator@0.1.4 node_modules/config-cev-generator ├── stdio@0.2.7 └── config@1.15.0 $ npm test > cev-test@1.0.0 pretest /Users/matthew/temp/cev-test > npm run cev > cev-test@1.0.0 cev /Users/matthew/temp/cev-test > $(npm bin)/cev --prefix COOLNESS > config/custom-environment-variables.json > cev-test@1.0.0 test /Users/matthew/temp/cev-test > echo 'you should run tests here' you should run tests here $ cat config/custom-environment-variables.json { "dirs": { "upload": "COOLNESS_DIRS_UPLOAD", "extracted": "COOLNESS_DIRS_EXTRACTED", "regex": "COOLNESS_DIRS_REGEX", "format": "COOLNESS_DIRS_FORMAT" }, "tz": "COOLNESS_TZ", "webserver": { "name": "COOLNESS_WEBSERVER_NAME", "port": "COOLNESS_WEBSERVER_PORT", "pfx": "COOLNESS_WEBSERVER_PFX", "passphrase": "COOLNESS_WEBSERVER_PASSPHRASE", "public": "COOLNESS_WEBSERVER_PUBLIC", "ca": "COOLNESS_WEBSERVER_CA", "cakey": "COOLNESS_WEBSERVER_CAKEY" }, "encryption": { "public": { "keyfile": "COOLNESS_ENCRYPTION_PUBLIC_KEYFILE", "key": "COOLNESS_ENCRYPTION_PUBLIC_KEY" } }, "email": { "username": "COOLNESS_EMAIL_USERNAME", "password": "COOLNESS_EMAIL_PASSWORD", "from": "COOLNESS_EMAIL_FROM", "host": "COOLNESS_EMAIL_HOST", "port": "COOLNESS_EMAIL_PORT" }, "ios": { "root": "COOLNESS_IOS_ROOT", "plist": "COOLNESS_IOS_PLIST", "ipa": "COOLNESS_IOS_IPA", "bundleIdentifier": "COOLNESS_IOS_BUNDLEIDENTIFIER", "subtitle": "COOLNESS_IOS_SUBTITLE", "title": "COOLNESS_IOS_TITLE" } } |
You're welcome. And long live laziness.
The npm module is at https://www.npmjs.com/package/config-cev-generator and the public git repo is at https://github.com/SciSpike/node-config-cev-generator. Issues welcome; pull requests are more welcome! 🙂
---
Matthew Adams
For more information, please contact info@scispike.com
Lightning Fast Software Development!