You’re Doing Environment Variables All Wrong – A Node.js Perspective



TL;DR

Setting variables aren’t all the time what you anticipate and it’s painful to test every one. As an alternative, use a library similar to safe-env-vars to do the onerous work and be protected within the data your surroundings variables received’t trigger you any complications.



Oh, what?

Setting variables are simple, you say, we’ve been working with surroundings variables for our total careers… how might we doable be “doing them mistaken”?! Effectively, as American pc scientist Jim Horning stated, “Nothing is so simple as we hope it is going to be”. And on this case a threat is launched each time you ‘set and neglect’ a variable. Let’s discover the issue, or relatively, issues.



Let’s begin on the prime

So what are surroundings variables and why will we use them? Put merely, surroundings variables are items of state (learn; string values) that we retailer within the ‘surroundings’ that our utility is operating in. This state is often set through one of many mechanisms offered by the working system, shell, or container orchestrator, which is answerable for our utility course of.

Setting variables are a easy mechanism, and that is good factor as a result of lots of engineering just isn’t so easy.

“Simplicity is prerequisite for reliability. “ — Edsger Dijkstra.

Typically in engineering we have to iteratively refactor and rework our options till we attain an excellent steadiness between readability and performance. Right here, simplicity is our pal as a result of it makes it simpler to know what our code is doing and why. We’re far much less more likely to find yourself with misbehaving, buggy software program if it’s easy.



See, it’s largely upside!

Effectively sure, there’s an terrible lot of upside. As we will see storing state within the surroundings permits us to do a number of very helpful issues that will in any other case be dangerous or time consuming.



1. Change configuration at will

We will change the behaviour of our utility while avoiding dangerous actions similar to altering supply code, and time consuming chores similar to re-compiling, re-deploying, testing, and so forth. If we have to rotate API keys, flip function flags on or off, or modify another behaviour, we are able to do all this from the consolation of our chairs just by deploying the brand new values and restarting our purposes.



2. Hold secrets and techniques hidden

We will retailer secrets and techniques individually to our supply code. This helps us mitigate the danger of delicate values similar to API keys, credentials, and so forth that will put our customers in danger in the event that they have been to be uncovered. This manner, if a nefarious actor good points entry to our supply code, they received’t get their palms on the secrets and techniques on the identical time. It makes it tougher for them to do us injury.



3. Keep on the correct facet of regulation

In regulated industries it’s typically essential to restrict personnel entry to delicate techniques to a restricted variety of particular folks. By storing the secrets and techniques individually to the supply code, the engineers can nonetheless do their jobs successfully with out the keys to the dominion sitting inside their attain.



4. Set totally different values per engineer or surroundings

While working regionally we frequently want to make use of totally different values for API keys, function flags, and behavior flags that make sense while growing however not in deployed environments. The identical could be stated of automated testing the place assessments may have to vary the applying’s behaviour and inputs to check specific points.

Every deployed surroundings could be given a unique set of surroundings variables, for example to maintain manufacturing secrets and techniques remoted and separate from staging secrets and techniques. As with native improvement, we are able to additionally change the values in our staging/testing environments independently of the opposite environments as wanted. Flexibility is nice!



5. Use dot env information

Within the expansive JavaScript universe a standard sample is to make use of the dot-env bundle to learn in surroundings variables from an area .env file that isn’t dedicated to the repository. This can be a a lot faster (and importantly extra seen) various to setting surroundings variables within the precise surroundings. Engineers can change the values shortly and simply while growing as the necessity arises.



So what’s the issue?

There are just a few. These are all dangers that we have to mitigate for, vulnerabilities that may depart us open to assault, and errors that may trigger sudden behaviour on the worst instances. Even in one of the best case state of affairs, badly behaving surroundings variables can waste a big period of time, particularly in dynamically typed languages similar to JavaScript.

“Search simplicity however mistrust it.” — Alfred North Whitehead.

We have to be cautious to not fall in to one of many myriad traps. In every case, it’s onerous if not unimaginable to foretell how our utility will behave. Generally points are instantly apparent, however in lots of situations we received’t find out about a problem till it randomly rears its head on the most inconvenient time.



1. Lacking values

The obvious threat right here is {that a} worth may very well be lacking. That is extra more likely to be the case on our native machines the place one developer makes a change that requires an surroundings variable we haven’t bought set in our native surroundings. It’s much less more likely to occur in deployed code which has gone by way of a number of layers of evaluations and testing, however it could actually nonetheless occur with complicated techniques. We’re solely human in spite of everything!

LOG_LEVEL="TRACE"
#API_KEY="..."
DATABASE_URL="..."
Enter fullscreen mode

Exit fullscreen mode

Oops, we disabled the API_KEY worth and forgot about it. Or maybe our colleague added ACCESS_TOKEN_TTL of their newest commit and also you haven’t observed it’s worthwhile to add it to your native .env file.



2. Empty values

Much like lacking values, it’s doable for the worth of an surroundings variable to finish up as an empty string. Maybe that was intentional (although it in all probability shouldn’t be), however how would we all know?

LOG_LEVEL=""
Enter fullscreen mode

Exit fullscreen mode

What precisely does the above imply to you? Does it imply we need to flip logging off totally? Does it imply we need to use the default log stage and we don’t care what it’s? Or (extra probably) has one thing damaged that we have to repair? Ask your folks, you may discover they’ve diverging expectations to you.



3. Arbitrary values

Setting variables are sometimes used for boolean values similar to function flags. Booleans have some huge downsides which I received’t go into right here, however protected to say these boolean values are arbitrary and totally different engineers will use totally different values.

For instance:

FEATURE_FLAG_AAA="true"
FEATURE_FLAG_B="TRUE"
FEATURE_FLAG_c="sure"
FEATURE_FLAG_c="Y"
FEATURE_FLAG_c="1"
Enter fullscreen mode

Exit fullscreen mode

As people, we immediately know that every one these values all signify the identical factor, {that a} specific function flag has been toggled on. We depend on conventions and consistency to make sure we don’t fall into the entice of utilizing totally different values somewhere else, however good intentions received’t all the time assist when herding cats 🐈 (engineers).

The identical could be stated should you use enum values, similar to with log ranges (INFO, DEBUG, TRACE, and many others). Clearly you can find yourself with an invalid worth that will throw a spanner within the works until you validate the worth you learn from the variable… however how many people actually try this? 🌚



4. Incorrect sorts

We lined the issue with boolean values above, it’s an identical story if it’s worthwhile to use a price as a quantity. Setting variables are all the time learn in as strings no matter what worth you’ve saved in them:

FEATURE_FLAG_AAA="true"
SOME_NUMBER="3"
Enter fullscreen mode

Exit fullscreen mode

Perhaps you want the SOME_NUMBER worth to be a quantity so TypeScript will assist you to cross it to the good library you need to use. Do you parse the worth to an integer like this?

const worth = Quantity.parseInt(course of.env.SOME_NUMBER);
someNiceLibrary(worth);
Enter fullscreen mode

Exit fullscreen mode

And what if that worth will get modified to a float in a single surroundings however not one other?

SOME_NUMBER="3.14"
Enter fullscreen mode

Exit fullscreen mode

Abruptly your utility is freaking out however you don’t know why. Your seeing some bizarre behaviour however you don’t know why, or maybe worse, you’re seeing an error message stack hint that could be a purple herring and factors you completely within the mistaken direct for an hour while your buyer is yelling at you.

You may argue that this challenge is extra more likely to happen in JavaScript than different languages, however sudden behaviour is all the time a threat when coping with unwanted side effects like surroundings variables.



5. Non-obligatory values

One other consideration is that generally we actually do need values to be elective, the place issues like the next could also be completely legitimate given our context:

#FEATURE_FLAG_AAA="true" # 1. remark out a price we do not want in the mean time.
FEATURE_FLAG_AAA="" # 2. or set it to an empty worth (not so good!)
Enter fullscreen mode

Exit fullscreen mode

If we’re manually checking surroundings variables to make sure they exist we have to depart this one variable unchecked as it might be elective. This introduces the human factor whereby future engineers might not add in presence checks the place wanted as a result of they see they aren’t constantly utilized to all variables. The variable is implicitly elective and this leaves it open to interpretation by the reader. Higher to be express when variables are elective as the bulk (i.e. the default) can be required.



6. Hidden surroundings variables

It’s a poor (however sadly widespread) apply for engineers to learn in an surroundings variable on the level they need to use it, for example:

operate calculateCommission(quantity: quantity): quantity {
  return quantity * Quantity.parseInt(course of.env.COMMISSION_RATE);
}
Enter fullscreen mode

Exit fullscreen mode

What’s the issue right here? Effectively our good calculateCommission operate can exhibit odd behaviour if our COMMISSION_RATE surroundings variable is lacking or set to some bizarre worth. Maybe the engineer that wrote this forgot to replace the documentation to point that the fee price must be configured within the surroundings and also you didn’t realise you wanted to do it. Whoops.



7. Behaviour and safety

Setting variables are unwanted side effects. You may say they add impurities to our code. Our utility can’t management the values it’s studying from the surroundings and should settle for what it’s given. This implies surroundings variables are akin to consumer enter and carry the identical dangers. ☠️

The worth of an surroundings variable may very well be sudden, or worse, malicious. Finest case, the worth triggers a visual error that leads you down the backyard path for an hour or two earlier than you determine what’s really inflicting the problem. Worst case, you’ve gotten uncovered your utility to enter you may’t belief (and you’ve got trusted it completely) with out verifying it’s authenticity or correctness, and now you’ve gotten been storing delicate knowledge within the attacker’s message queue for the final 2 weeks relatively than your personal. 😬



Proper, how will we sidestep these points?

Simplicity is fantastically splendiferous, besides when it’s not.

“Easy could be tougher than complicated: You need to work onerous to get your considering clear to make it easy. Nevertheless it’s price it ultimately as a result of when you get there, you may transfer mountains.” — Steve Jobs.

The trick as with all ‘consumer’ enter exterior our sphere of management, is to belief however confirm, or in our case, belief however validate. There are some things you need to do for each worth you learn in from the surroundings:

  1. Presence checks – guarantee anticipated surroundings variables are outlined.
  2. Empty checks – guarantee anticipated values will not be empty strings.
  3. Worth checks – guarantee solely anticipated values could be set.
  4. Typecasting – guarantee values are solid to the anticipated kind on the level you learn them in.
  5. Single entry level – guarantee all variables are pulled in on the identical place, and never smeared round your codebase for folks to come upon later.
  6. Dot env – learn values from each a .env file and the surroundings.

Writing the code to do that for each venture could be a ache, however the excellent news is, I’ve already performed that for you.



Package deal: safe-env-var

safe-env-vars will learn surroundings variables from the surroundings in addition to a .env file in a protected manner with full TypeScript help. By default, it would throw an error if the surroundings variable you are attempting to learn is undefined or empty.

It’s very fast to get began with primary utilization if all you’re doing is studying in string values which are all the time required:

import EnvironmentReader from 'safe-env-vars';

const env = new EnvironmentReader();

export const MY_VALUE = env.get(`MY_VALUE`); // string
Enter fullscreen mode

Exit fullscreen mode

You’ll be able to explicitly mark variables as elective:

export const MY_VALUE = env.elective.get(`MY_VALUE`); // string | undefined
Enter fullscreen mode

Exit fullscreen mode

Or you may enable the variables to be an empty worth, although I might discourage this for the explanations said within the dialogue above:

export const MY_VALUE = env.get(`MY_VALUE`, { allowEmpty: true }); // string
Enter fullscreen mode

Exit fullscreen mode

You’ll be able to even solid the kind of the worth as you’d anticipate:

// Required
export const MY_BOOLEAN = env.boolean.get(`MY_BOOLEAN`); // boolean
export const MY_NUMBER = env.quantity.get(`MY_NUMBER`); // quantity

// Non-obligatory
export const MY_BOOLEAN = env.elective.boolean.get(`MY_BOOLEAN`); // boolean | undefined
export const MY_NUMBER = env.elective.quantity.get(`MY_NUMBER`); // quantity | undefined
Enter fullscreen mode

Exit fullscreen mode

And eventually, you may need to test whether or not the variable is among the allowed values. This test all the time happens after the presence/empty checks and typecasting the worth.

export const MY_NUMBER = env.quantity.get(`MY_NUMBER`, { allowedValues: [1200, 1202, 1378] ); // quantity
Enter fullscreen mode

Exit fullscreen mode

See the docs for extra utilization data and examples.



Really helpful sample

I might advocate you’ve gotten a single level of entry for the surroundings variables in your utility. One place the place you learn in all of the values wanted by the totally different modules and capabilities. This ensures that there’s just one place to look and one place to vary when making modifications.

I wish to construction my single level of entry in JavaScript/TypeScript initiatives like this:

/src/
    /important.ts
    /config/
        /env.ts
        /constants.ts
        /index.ts
Enter fullscreen mode

Exit fullscreen mode



./config/env.ts

import EnvironmentReader from 'safe-env-vars';

const env = new EnvironmentReader();

export const COMMISSION_RATE = env.quantity.get(`COMMISSION_RATE`); // quantity
Enter fullscreen mode

Exit fullscreen mode



./config/constants.ts

export const SOME_CONSTANT_VALUE = 123;
export const ANOTHER_CONSTANT_VALUE = `Hi there, World`;
Enter fullscreen mode

Exit fullscreen mode



./config/index.ts

export * as env from './env';
export * as constants from './constants';
Enter fullscreen mode

Exit fullscreen mode



…and the utilization?

import * as config from './config';

const { COMMISSION_RATE } = config.env;
const { SOME_CONSTANT_VALUE } = config.constants;

export operate calculateCommission(quantity: quantity): quantity {
  return quantity * COMMISSION_RATE;
}
Enter fullscreen mode

Exit fullscreen mode

This ends in a really clear manner of working with configurable surroundings variables in addition to fixed values. The advantages of this method are that there’s a single level of entry for the surroundings variables in your utility, and each utilization of those values directs the reader again to that entry level.



Conclusion

Don’t fall into the entice of believing that since you’ve been utilizing surroundings variables for years that they’re protected and might’t shock you. It’s higher to belief however confirm the values you’re studying utilizing a strong and time-saving library similar to safe-env-vars* which does the onerous be just right for you.

*Various choices might exist. 🙃

Add a Comment

Your email address will not be published. Required fields are marked *