DEV Community

kaisukez
kaisukez

Posted on • Updated on

My favorite way to manage config file (javascript example)

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 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.

const fs = require('fs')
const path = require('path')
const dotenv = require('dotenv')
const dotenvExpand = require('dotenv-expand')
Enter fullscreen mode Exit fullscreen mode

You need 2 libraries which are dotenv and dotenv-expand.

if (!process.env.NODE_ENV) {
    process.env.NODE_ENV = 'development'
}
Enter fullscreen mode Exit fullscreen mode

You can throw error if you want but I prefer to use development environment as a default.

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
Enter fullscreen mode Exit fullscreen mode

This function returns absolute path of .env file which is relative to the directory that contains this file.

function getNodeEnv() {
    return process.env.NODE_ENV.trim()
}
Enter fullscreen mode Exit fullscreen mode

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).

const dotenvFiles = [
    `${getEnvPath()}.${getNodeEnv()}.local`,
    getNodeEnv() !== 'test' && `${getEnvPath()}.local`,
    `${getEnvPath()}.${getNodeEnv()}`,
    getEnvPath(),
].filter(Boolean)
Enter fullscreen mode Exit fullscreen mode

The code above is the array that contains order of .env files to load. The order came from this link, I believe it is best practice of some framework and that practice is popular among us (developers).

dotenvFiles.forEach(dotenvFile => {
    if (fs.existsSync(dotenvFile)) {
        dotenvExpand(
            dotenv.config({
                path: dotenvFile,
            })
        )
    }
})
Enter fullscreen mode Exit fullscreen mode

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.

// 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,
            })
        )
    }
})
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode
# /.env.development.local
CONFIG_2 = YOUR_CONFIG_2
CONFIG_WITH_SPACE = "    SPACE"
Enter fullscreen mode Exit fullscreen mode

2) Include environments.js to your entry point of your project (in this case, it's /src/index.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)
Enter fullscreen mode Exit fullscreen mode

3) Run your code with development environment

NODE_ENV=development node src/index.js
Enter fullscreen mode Exit fullscreen mode

Here's the result.

SHARED_CONFIG_1
YOUR_CONFIG_2
SHARED_CONFIG_1+YOUR_CONFIG_2
    SPACE
Enter fullscreen mode Exit fullscreen mode

Don't forget to add .gitignore file

# /.gitignore
.env.local
.env.development.local
.env.test.local
.env.production.local
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
dotenv profile image
Dotenv

💛🌴