(In which I make the argument that using a configuration file (or two) is a better idea than setting the NODE_ENV environment variable and propose the use of the SN Props package to make this process easier.)
Like everyone else in the node community, I started out using the NODE_ENV environment variable to set various details in my apps. I would do something like this to start an app:
$ NODE_ENV=dev /usr/bin/node foo.js
The shell would set the environment variable NODE_ENV to the string "dev" and launch the foo.js node application. Inside foo.js, we would do something like this:
var port, host; if( "prod" === process.env.NODE_ENV ) { port = 80; host = "0.0.0.0"; } else if( "dev" === process.env.NODE_ENV ) { port = 8080; host = "127.0.0.1"; } else { console.log( "ERROR: unsupported NODE_ENV value: " + process.env.NODE_ENV ); process.exit(1); } // insert other code here server.listen( port, host );
And honestly, there's nothing seriously wrong with this. It lets you express different application behavior based on whether you're running in development-mode or if you've deployed to production. But then I saw this:
var port, host, db_pass; if( "prod" === process.env.NODE_ENV ) { port = 80; host = "0.0.0.0"; db_pass = "tq6TJwFR"; } else if( "dev" === process.env.NODE_ENV ) { port = 8080; host = "127.0.0.1"; db_pass = "2qpwRfKB"; } else { console.log( "ERROR: unsupported NODE_ENV value: " + process.env.NODE_ENV ); process.exit(1); } // insert code to connect to the database here server.listen( port, host );
And where did I see this? In the source repository, of course.
Now I don't want to be too snobbish, I've written apps that check the password into the source repo before. At the time I thought it was a simple, throw-away app that no one would use after a couple months. By the time the black-hats found this in the source repo, the app would be long-retired. Except that's never the way the world works. The code got used by a different group in the organization and then sold to a third party. By the time I heard what had happened, one of the third parties had lined up a SAS-70 audit they may have failed because of a fixed password in the system.
Moral of the story? Don't hard-code database passwords into your app.
"But what does this have to do with NODE_ENV being an anti-pattern?" you ask.
Simple, using NODE_ENV to set application behavior makes it easy for a developer to do bad things (like hard-coding dev & production environment passwords into the app.) In my apps, I've replaced the use of NODE_ENV with what I call the "Concatenated Config" pattern.
Instead of setting my config parameters from an environment variable inside the code, I use multiple JSON files to hold config settings and import them as a javascript object. The SN Props package does all the heavy lifting for you. So now, I launch an app like this:
$ node ./bar.js \ --config file:///opt/bar/dev.json \ --config file:///opt/bar/db_dev.json
SN Props reads the command line looking for URLs pointing to JSON files. In this example, it grabs the files /opt/bar/dev.json and /opt/bar/db_dev.json, smooshes them together and passes them to your app via a callback. Here's what the code in bar.js looks like:
require( 'sn-props' ).read( function( props ) { // db connection setup code goes here // http server code goes here } );
And the contents of the two config files look something like this:
/opt/bar/production.json:
{ "listen": { "port": 8080, "host": "127.0.0.1" } }
/opt/bar/db_db002.json:
{ "mysql": { "host": "127.0.0.1", "port": 3306, "user": "dev", "pass": "goats!" } }
and, of course, you probably want to define other configs to use in production:
/opt/bar/production.json:
{ "listen": { "port": 80, "host": "0.0.0.0" } }
/opt/bar/db_db002.json:
{ "mysql": { "host": "db002.internal.example.com", "port": 3306, "user": "bar", "pass": "JC7VwyguUqHm8D3J" } }
And if you trust your internal infrastructure, you can replace references to file: URLs with references to http: URLs. You can probably figure out what this does:
$ node ./bar.js \ --config https://config.example.com/dev.json \ --config file:///opt/bar/db_dev.json
Let's Recap:
The benefits of this pattern are:
There are a few draw-backs, but they're relatively mild:
In my experience, the benefits outweigh the drawbacks. I no longer embed application policy in the code itself; instead, i put it in a configuration file that's has slightly different access control and logging settings.