Friday, October 11, 2019

NodeJS Configuration per Environment

Dotenv is a zero-dependency module that loads environment variables from a .env file into process.env. Storing configuration in the environment separate from code is based on The Twelve-Factor App methodology.
Reference: https://github.com/motdotla/dotenv

While you can use NodeJS Dotenv to manage configurations per environment,
another approach is to create a configuration per environment, and use an environment based file to distinguish which configuration to use. 
Why? 
It is useful to actually version control your configuration, so every developer and every instance of your application has the same configuration keys and values.

An example of how to setup a production and staging environment follows.

Create the environment file as root to minimize the odds of the environment being removed or changed

> cd /home/yuourapp/
> sudo touch env-prod

> cd /home/yuourappstg/
> sudo touch env-stg


Helper scripts to start and restart your NodeJS Forever service

Note: [ -f "env-stg" ] returns true if the file exists

> start-yourapp.sh
#!/bin/bash

if [ -f "env-stg" ]; then
    forever start -a --minUptime 1000 --spinSleepTime 2000 --uid yourapp-stg yourapp.js
else
    forever start -a --minUptime 1000 --spinSleepTime 2000 --uid yourapp yourapp.js
fi


> restart-yourapp.js
#!/bin/bash

if [ -f "env-stg" ]; then
    forever restart yourapp-stg
else
    forever restart yourapp
fi


Create a configuration file per environment, ensuring that each configuration has the same keys, and varying the values as appropriate.

> config/config-stg.js
module.exports = {
port : 9011,
log : {
console : { level : 'silly' }
}
};

> config/config-prod.js
module.exports = {
port : 9001,
log : {
console : { level : 'error' }
}
};


Create a base configuration script to read in the appropriate configuration file

An example for a NodeJS process
> config/config.js
const path = require('path');
const fs = require('fs');

let env;
// check for env-stg or env-prod file
if (fs.existsSync('env-stg')) {
env = 'stg';
} else {
env = 'prod';
}
const configPath = path.resolve(process.cwd(), `config/config-${env}`);
const config = require(configPath);
// visual validation of correct env
console.log('Using config ' + configPath);
module.exports = config;


An example for a VueJS/Nuxt process

Due to VueJS/Nuxt being a browser based solution,
to avoid warnings and errors, create another env file to be required

> sudo vi env
module.exports = {
    env: 'stg'
}

Add configuration as needed
> nuxt.config-stg.js
module.exports = {
server: { port: 9013 },
};

> nuxt.config-prod.js
module.exports = {
server: { port: 9003 },
};

Create a base configuration script to read in the appropriate configuration file.
> nuxt.config.js
const path = require('path');

// check for env file
let env;
try {
// (base)/
// require ('./env-stg');
// using exports, as when require from .vue, build causes warning 'Module not found: Error: Can't resolve './env-stg''
env = require ('./env');
env = env.env;
} catch (e) {
// default prod
env = 'prod';
}

// check for env based nuxt config when called from different relative paths
let configPath;
let config;
try {
// (base)/
configPath = `./nuxt.config-${env}.js`;
// config = require(configPath); // on build, results in warning 'Critical dependency: the request of a dependency is an expression'
config = require(`./nuxt.config-${env}.js`);
} catch (e) {
try {
// (base)/server/
configPath = `../nuxt.config-${env}.js`;
config = require(`../nuxt.config-${env}.js`);
} catch (e) {
// (base)/pages/dir/
configPath = `../../nuxt.config-${env}.js`;
config = require(`../../nuxt.config-${env}.js`);
}
}

// visual validation of correct env
console.log('Building nuxt using ' + configPath);
module.exports = config;

Now you can check in the configuration files, but do not check in the .env and env-stg, env-prod files (add them to .gitignore), as those should vary based on the deployed environment.


-End of Document-
Thanks for reading