My favorite way to manage config file (javascript example)
Tags: webdev, javascript
Published: 2021-05-10 18:03:35

My favorite way to manage configuration file is to use _.env_ (dotenv).

Because
1. You have shared config file with your team.
2. But you also have your own private config file which is ignored by git.
3. You can have multiple config files for each deployment types (e.g., development, production, test).
4. It is (kind of) a [standard](https://12factor.net/config) way of managing configuration. So most of the time, it will work with many other tools seamlessly (e.g., Docker).

Now I will show you my code. But first I want to tell you that I got this inspiration from [create-react-app source code](https://github.com/facebook/create-react-app/blob/fddce8a9e21bf68f37054586deb0c8636a45f50b/packages/react-scripts/config/env.js).





```js
const fs = require('fs')
const path = require('path')
const dotenv = require('dotenv')
const dotenvExpand = require('dotenv-expand')
```
You need 2 libraries which are [dotenv](https://www.npmjs.com/package/dotenv) and [dotenv-expand](https://www.npmjs.com/package/dotenv-expand).




```js
if (!process.env.NODE_ENV) {
    process.env.NODE_ENV = 'development'
}
```
You can throw error if you want but I prefer to use development environment as a default.



```js
function getEnvPath() {
    return path.resolve(__dirname, '../', '.env')
}

// my folder structure looks like this
// /src/environments.js (this file)
// /src/index.js (entry point)
// /.env
// /.env.development
// /.env.development.local
// /.gitignore
```
This function returns absolute path of _.env_ file which is relative to the directory that contains this file.



```js
function getNodeEnv() {
    return process.env.NODE_ENV.trim()
}
```
The function above is optional. But I've found a bug when I used windows to run code with CMD, the NODE_ENV gave me an extra spacebar. So I use _.trim()_ to fix that bug (I don't know whether the bug still exists today because I moved to WSL already).




```js
const dotenvFiles = [
    `${getEnvPath()}.${getNodeEnv()}.local`,
    getNodeEnv() !== 'test' && `${getEnvPath()}.local`,
    `${getEnvPath()}.${getNodeEnv()}`,
    getEnvPath(),
].filter(Boolean)
```
The code above is the array that contains order of .env files to load. The order came from this [link](https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use), I believe it is best practice of some framework and that practice is popular [among us](https://store.steampowered.com/app/945360/Among_Us/) (developers).




```js
dotenvFiles.forEach(dotenvFile => {
    if (fs.existsSync(dotenvFile)) {
        dotenvExpand(
            dotenv.config({
                path: dotenvFile,
            })
        )
    }
})
```
Then what's this code doing is loading each file sequentially from the order of _dotEnvFiles_ array.



This is full code of the _environments.js_ file.
```js
// environments.js
// Inspiration from https://github.com/facebook/create-react-app/blob/fddce8a9e21bf68f37054586deb0c8636a45f50b/packages/react-scripts/config/env.js


const fs = require('fs')
const path = require('path')
const dotenv = require('dotenv')
const dotenvExpand = require('dotenv-expand')

if (!process.env.NODE_ENV) {
    process.env.NODE_ENV = 'development'
}

function getEnvPath() {
    return path.resolve(__dirname, '../', '.env')
}

function getNodeEnv() {
    return process.env.NODE_ENV.trim()
}


// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
const dotenvFiles = [
    `${getEnvPath()}.${getNodeEnv()}.local`,
    // Don't include `.env.local` for `test` environment
    // since normally you expect tests to produce the same
    // results for everyone
    getNodeEnv() !== 'test' && `${getEnvPath()}.local`,
    `${getEnvPath()}.${getNodeEnv()}`,
    getEnvPath(),
].filter(Boolean)


// Load environment variables from .env* files. Suppress warnings using silent
// if this file is missing. dotenv will never modify any environment variables
// that have already been set.  Variable expansion is supported in .env files.
// https://github.com/motdotla/dotenv
// https://github.com/motdotla/dotenv-expand
dotenvFiles.forEach(dotenvFile => {
    if (fs.existsSync(dotenvFile)) {
        dotenvExpand(
            dotenv.config({
                path: dotenvFile,
            })
        )
    }
})
```






Finally I will show you how to use this new cool stuff.

1) Create _.env_ files
```
# /.env.development
CONFIG_1 = SHARED_CONFIG_1
CONFIG_2 = SHARED_CONFIG_2
EXPANDED_CONFIG = ${CONFIG_1}+${CONFIG_2}
```
```
# /.env.development.local
CONFIG_2 = YOUR_CONFIG_2
CONFIG_WITH_SPACE = "    SPACE"
```

2) Include _environments.js_ to your entry point of your project (in this case, it's _/src/index.js_)
```js
// /src/index.js
require('./environments')

console.log(process.env.CONFIG_1)
console.log(process.env.CONFIG_2)
console.log(process.env.EXPANDED_CONFIG)
console.log(process.env.CONFIG_WITH_SPACE)
```

3) Run your code with _development_ environment
```sh
NODE_ENV=development node src/index.js
```
Here's the result.
```
SHARED_CONFIG_1
YOUR_CONFIG_2
SHARED_CONFIG_1+YOUR_CONFIG_2
    SPACE
```

Don't forget to add .gitignore file
```
# /.gitignore
.env.local
.env.development.local
.env.test.local
.env.production.local
```