2
\$\begingroup\$

In the code below, I am building a NODE_ENV-sensitive config object from environment variables.

let username
let password
let cluster
let hosts
let databaseName
let replicaSet

if (process.env.NODE_ENV === 'production') {
  username = process.env.ATLAS_HUB_USERNAME
  password = process.env.ATLAS_HUB_PASSWORD
  cluster = process.env.ATLAS_CLUSTER
  hosts = process.env.ATLAS_HOSTS
  databaseName = process.env.ATLAS_DATABASE
  replicaSet = process.env.ATLAS_REPLICA_SET
} else {
  username = process.env.MONGO_HUB_USERNAME
  password = process.env.MONGO_HUB_PASSWORD
  cluster = process.env.MONGO_CLUSTER
  hosts = process.env.MONGO_HOSTS
  databaseName = process.env.MONGO_DATABASE
  replicaSet = process.env.MONGO_REPLICA_SET
}
const config = { username, password, cluster, hosts, databaseName, replicaSet }

In this new age of fancy spread and rest operators, I hate polluting my files with code like this which repeat the same variable name multiple times and uses let instead of const for the wrong reasons, all for something simple and frequent. I could use the ternary operator to get something way better:

const config = {
  username:     process.env.NODE_ENV === 'production' ? process.env.ATLAS_HUB_USERNAME : process.env.MONGO_HUB_USERNAME,
  password:     process.env.NODE_ENV === 'production' ? process.env.ATLAS_HUB_PASSWORD : process.env.MONGO_HUB_PASSWORD,
  cluster:      process.env.NODE_ENV === 'production' ? process.env.ATLAS_CLUSTER : process.env.MONGO_CLUSTER,
  hosts:        process.env.NODE_ENV === 'production' ? process.env.ATLAS_HOSTS : process.env.MONGO_HOSTS,
  databaseName: process.env.NODE_ENV === 'production' ? process.env.ATLAS_DATABASE : process.env.MONGO_DATABASE,
  replicaSet:   process.env.NODE_ENV === 'production' ? process.env.ATLAS_REPLICA_SET : process.env.MONGO_REPLICA_SET
}

But even this seems one step short of what modern js should be able to do. I'd now like to get rid of the repeated process.env.NODE_ENV, I just can't figure out how (apart from creating a new const with a shorter name). If I had a magic wand, I'd write something along the following lines:

const config = ({
  username: [process.env.MONGO_HUB_USERNAME, process.env.ATLAS_HUB_USERNAME],
  password: [process.env.MONGO_HUB_PASSWORD, process.env.ATLAS_HUB_PASSWORD],
  cluster: [process.env.MONGO_CLUSTER, process.env.ATLAS_CLUSTER],
  hosts: [process.env.MONGO_HOSTS, process.env.ATLAS_HOSTS],
  databaseName: [process.env.MONGO_DATABASE, process.env.ATLAS_DATABASE],
  replicaSet: [process.env.MONGO_REPLICA_SET, process.env.ATLAS_REPLICA_SET]
}).*[new Number(process.env.NODE_ENV === 'production')]

But I don't, and it's not even all that great, sooo, any suggestions?

I thought of using a function, like below, but this just introduces another dependency you need to internalize for a simple batch conditional assignment operation... And if the function is in-line, there is duplication across files and it's frankly just confusing.

function fromEach (obj, key) {
  const final = {}
  Object.keys(obj).forEach((k) => {
    final[k] = obj[k][key]
  })
  return final
}
const config = fromEach({
  username: [process.env.MONGO_HUB_USERNAME, process.env.ATLAS_HUB_USERNAME],
  password: [process.env.MONGO_HUB_PASSWORD, process.env.ATLAS_HUB_PASSWORD],
  cluster: [process.env.MONGO_CLUSTER, process.env.ATLAS_CLUSTER],
  hosts: [process.env.MONGO_HOSTS, process.env.ATLAS_HOSTS],
  databaseName: [process.env.MONGO_DATABASE, process.env.ATLAS_DATABASE],
  replicaSet: [process.env.MONGO_REPLICA_SET, process.env.ATLAS_REPLICA_SET]
}, new Number(process.env.NODE_ENV === 'production'))

Note: I use new Number(process.env.NODE_ENV === 'production') in these examples, but I don't like it, feel free to propose something better!

\$\endgroup\$
1
\$\begingroup\$

Ideally, I would suggest two changes the the overall design:

  • the environment variable names could have one consistent prefix, e.g. MONGO_ and ATLAS_
  • the keys of the config object could match the environment variable names, e.g. databaseName would match MONGO_DATABASE_NAME

If that for some reason is not possible, then we can hardcode the config-to-environment name mapping:

function getConfigurationFor(prefix) {
  const configMap = {
    username:     'HUB_USERNAME',
    password:     'HUB_PASSWORD',
    cluster:      'CLUSTER',
    hosts:        'HOSTS',
    databaseName: 'DATABASE',
    replicaSet:   'REPLICA_SET'
  }

  prefix = prefix.toUpperCase() + '_'
  return Object
    .entries(configMap)
    .reduce((result, [key, envKey]) => {
      result[key] = process.env[prefix + envKey]
      return result
    }, {})
}

const config = getConfigurationFor(
  process.env.NODE_ENV === 'production'
    ? 'atlas'
    : 'mongo'
)

if you can make these design changes, then we have more flexibility: we can create a function that would read all the environment variables that start with a certain prefix and return this as a plain JavaScript object:

const getEnvironmentForPrefix(prefix, source) {
  const camelCase = require('lodash.camelcase')

  return Object
    .entries(source || process.env)
    .filter([key] => key.startsWith(prefix))
    .map(([key, value]) => [key.substring(prefix.length), value])
    .map(([key, value]) => [camelcase(key), value])
    .reduce((result, [key, value]) => {
      result[key] = value
      return result
    }, {})    
}

const config = getEnvironmentForPrefix(
  process.env.NODE_ENV === 'production'
    ? 'ATLAS_'
    : 'MONGO_'
)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.