The right way to use environment variables
It’s 2 PM, and you transformed all the caffeine into code and created a new bank integration feature. Everything works great on your local computer. You deployed the code. At 3 PM, there’s a P1 outage. Every time the customer clicks on “Add Bank Account”, the backend server stops responding at all. It doesn’t make sense.
Steps in, the intern. Trying to understand what’s wrong by asking questions. Did you use Pascal cases for class names? Did you miss a semicolon? Did you update the environment variables? Oh wait, yes, that’s it. Environment variables are missing.
The problem
There are three basic problems with managing environment variables as I see it.
- Forgetting to update the environment variable. Especially if one developer added a new environment variable, you don’t know you are missing one.
- The keys need to be typed manually and are prone to errors. Whenever you access an environment variable you have to do process. env[NAME_OF_ENV_VARIABLE], for example process.env.NODE_ENV or process.env.PORT. If the name of the variable is lengthy, it is hard to type, and easy to forget and you will have to check the .env file again and again. On top of that, there’s a high chance of a typo. Hence, The names need to be typed.
- Sharing the environment variables between all the developers in a team. Generally, up to a level, developers will just share the .env files through some secure communication channel. This doesn’t scale well for larger teams. Then they require solutions like key managers, but we won’t discuss solutions for this particular problem, as it’s on the easier side to deal with. Simply use something like AWS secrets manager or envssecret.
The solutions
A good solution is, if a marked required environment variable is missing, the server shouldn’t start at all. This way during the deployment of the server itself, the developer can know something is wrong. They won’t have to wait for an incident to determine that an environment variable is missing.
In my head solutions look like this → You start the server, an env is missing, and it throws a message on the command line, “HDFC_BANK_KEY is missing, can’t start the server”. Now you will know that you forgot to update the environment variable and you can do it and restart the server.
Show me to code, please!
Yes, let’s jump into the code. I prefer to create an envs.ts in the common code folder. Probably somewhere like /src/common/constants/envs.ts.
import * as dotenv from "dotenv";
// to access local .env
dotenv.config();
export const ENV_VARS = {
JSON_WEB_TOKEN: process.env.JSON_WEB_TOKEN,
AWS_ACCESS_KEY: process.env.AWS_ACCESS_KEY,
AWS_SECRET_KEY: process.env.AWS_SECRET_KEY,
AWS_BUCKET: process.env.AWS_BUCKET,
};
and let’s say your .env looks like this
JSON_WEB_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.VxLX20GhylVdY2ukQjCnT2K3tr1UzMc27TFQ-Ti3tLg"
AWS_ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE"
AWS_SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
AWS_BUCKET = "my-aws-bucket"
All you have to do is, for each environment variable in your .env file, you have to read it from process.env and then place it in this object. Then export it from this file. (Don’t forget to use dotnev).
Then wherever you need to access the env variables, instead of reading them from process.env, you can directly read them from this ENV_VARS object. Example: instead of doing process.env.JSON_WEB_TOKEN, you will do ENV_VARS.JSON_WEB_TOKEN. This will give type safety features and suggestions on your editor too. This solves the problem 2.
But what if another developer adds an env key, which I don’t have? How will we check this whenever we start the server?
Good question, I’m glad you asked! Let’s say developer A, added a new env key for Google authentication called “GOOGLE_API_KEY”. They will then have to add it in the envs.ts, file means now the envs.ts file looks like this —
import * as dotenv from "dotenv";
// to access local .env
dotenv.config();
export const ENV_VARS = {
JSON_WEB_TOKEN: process.env.JSON_WEB_TOKEN,
AWS_ACCESS_KEY: process.env.AWS_ACCESS_KEY,
AWS_SECRET_KEY: process.env.AWS_SECRET_KEY,
AWS_BUCKET: process.env.AWS_BUCKET,
GOOGLE_API_KEY: process.env.GOOGLE_API_KEY
};
Now when you start your local server, because your .env file doesn’t have GOOGLE_API_KEY the server should stop. To implement this, do this simple check
import { ENV_VARS } from "./common/constants/envs";
// This is the function to validate if any env keys are missing
// This function is pretty generic, so feel free to use in any language
function validateENVVariables() {
// Check if any ENV_VAR is missing
let anyKeyMissing = false;
Object.keys(ENV_VARS).forEach(key => {
if (!ENV_VARS[key] || ENV_VARS[key] === undefined) {
console.error(`environment variable ${key} is not in your .env file`);
anyKeyMissing = true;
}
});
if (anyKeyMissing) {
throw "MISSING ENVs, CAN'T START THE SERVER";
}
}
// This code is specific to Nest.js
async function bootstrap() {
validateENVVariables();
attachGlobalPropertiesToAppInstance(app);
setupSwaggerDocumentation(app);
await app.listen(process.env.PORT || 8000);
}
bootstrap()
.then(() => {
console.log("SUCCESSFULLY STARTED THE SERVER");
})
.catch(err => {
console.error(err);
});
Now when you start your server, you will see “environment variable GOOGLE_API_KEY is not in your .env file”. The server won’t start. This solves the Problem 1.
That’s it?
Yeah, pretty much that’s it.
Keeys your environment variable safe and away from interns. Happy coding.