Skip to content

MarcoDaniels/environment-decoder

Repository files navigation

environment decoder

Publish GitHub release

A decoder for the process.env

With environment-decoder you define a decoder for your environment variable names, and their corresponded types.

This library took a big inspiration from typescript-json-decoder, the main differences are that process.env is just a record type (no need for nested decoding) and since all process.env values default to string in environment-decoder you can cast the value to the desired type (see usage).

Idea (read struggle)

For most applications we have to trust that the environment flags are set correctly and often we see cases like:

server.listen(Number(process.env.PORT || 8080))

// or

// we are certain this is set by someone
if (process.env.FEATURE_FLAG!) {
    // ...
}

// or

// sure, this is fine
const baseURL = process.env.BASE_URL || ''
fetch(`${baseURL}/api`)
    .then(response => response.json())

This creates a lot of uncertainty: are the environment flags set? what are their "real" types?

Usage

Add it to your project

yarn add environment-decoder

// or

npm install environment-decoder

Since all environment variables are set as string, the decoder type primitives are written with asType as we will be casting (and validating) each variable.

Use .withDefault() to set default values for non-required environment variables.

import {environmentDecoder, asBoolean, asString, asNumber} from 'environment-decoder'

const myEnv = environmentDecoder({
    BASE_URL: asString,
    ENVIRONMENT: asStringUnion("dev","qa", "prod"),
    PORT: asNumber,
    FEATURE_FLAG: asBoolean,
    OPTIONAL_FLAG: asString.withDefault('OPTION_1')
})

// will output the process.env.BASE_URL value
console.log(myEnv.BASE_URL)

// will output the process.env.OPTIONAL_FLAG value if set, if not will output 'OPTION_1'
console.log(myEnv.OPTIONAL_FLAG) 

You can also use the output type created by environmentDecoder with DecodeType<typeof ...>:

import {environmentDecoder, asBoolean, asString, asNumber, DecodeType} from 'environment-decoder'

const myEnv = environmentDecoder({
    BASE_URL: asString,
    PORT: asNumber,
    FEATURE_FLAG: asBoolean
})

type MyEnvType = DecodeType<typeof myEnv>

const funWithEnv = (envParam: MyEnvType) => {
    console.log(envParam.FEATURE_FLAG)
}

Notes

environment-decoder will throw exceptions when:

  • the environment variable is not set and not using .withDefault() - all missing variables will be listed in the exception
  • the environment variable cannot be cast to type (ex: using asNumber on abcde)
  • using asStringUnion or asNumberUnion and the environment variable is not set to one of the allowed values

All exceptions will be thrown at run time, so it would be recommended to use environmentDecoder as close as possible to the entry point of the application in order to catch errors as early as possible.

Examples

Use it as React.js hook:

remember to use Error boundaries to handle exceptions thrown on your app

// useEnvironment.js
import {asString, environmentDecoder} from 'environment-decoder'

export const useEnvironment = function () {
    // if environment is not set will throw at this step
    return environmentDecoder({
        REACT_APP_MY_ENV_TEST: asString,
        NODE_ENV: asString
    })
}

// App.js
import {useEnvironment} from './useEnvironment'

function App() {
    const env = useEnvironment()
    return (
        <div>
            <h1>{env.REACT_APP_MY_ENV_TEST}</h1>
        </div>
    )
}

Use it in a Node.js application:

const fetch = require('node-fetch')
const {environmentDecoder, asString} = require('environment-decoder')

// if environment is not set will throw at this step
const config = environmentDecoder({
    FETCH_URL: asString,
    ACCESS_TOKEN: asString
})

const response = await fetch(config.FETCH_URL, {
    method: 'get',
    headers: {
        'Authorization': `Bearer ${config.ACCESS_TOKEN}`,
        'Content-type': 'application/json'
    }
})

const messageData = await response.json()