In server-side development, it's extremely common to access variables from the execution environment.
In this post, I hope to convince you to consolidate access of these variables into one single-file (or a more structured way of accessing these values) to make it easier to refactor, maintain, and update as your project grows.
// Logging to console the stage we're on
console.log(`This is the ${process.env.NODE_ENV}`);
Why is it useful to access variables from the environment?
I won't dive too much into the why of this, but you typically access sensitive values via this way, such as
- API keys and secrets
- Application identifiers
- Stage of the environment (looking at you
NODE_ENV
) - JSON Web Token key
- Database access credentials
- other top-secret values of this nature
These are values that you do not want committed to a version control system like GitHub and so you keep them out of there for security purposes.
You might also keep these out of there because these vary from stage to stage, and thus makes no sense to keep in GitHub.
So, getting them during runtime of the program it is! 😃
What's the issue with process.env?
In your own projects, you might be accessing environment variables through process.env.MY_VARIABLE
. This is great! It's fine, and it works.
But is it optimal?
Imagine you have two files that access the same environment variable, some sort of API key
// Usage 1
axios({
url: `${process.env.CMS_URL}/random-endpoint-1`/v1/random-endpoint-1`
header: `Bearer ${process.env.MY_API_KEY}`
});
// ...
// Usage 2
axios({
url: `${process.env.CMS_URL}/random-endpoint-1`/v1/random-endpoint-2`
header: `Bearer ${process.env.MY_API_KEY}`
});
Both of these files are accessing the same API key from the environment directly. Now imagine your projects expands in scale and you have many more instances where this API key needs to be accessed.
See the problem that might occur? You now would process.env.MY_API_KEY
littered throughout your project.
What if you need to change the environment variable from process.env.MY_API_KEY
to process.env.TWITTER_API_KEY
?
- Yes, you can easily rename all instances (using a powerful editor like VS Code). But this is going to cause a pretty large commit created for this simple change.
What if you have a plethora environment variables, and you want to group them? Like API credentials, database credentials, etc.?
- There's no way to do this with the normal
process.env.XXX_YYY
usage. Everything is at the same level and there's no grouping them.
What if you want to add context to each environment variable, so engineers can understand what purpose they serve?
- You can do this in your
.env.template
file as single-line comments, but this won't show up in the IDE as a hint or documentation for your team members.
How should we be accessing the environment variables?
I won't say you 100% definitively, absolutely, should follow my advice. But I think it can help prevent the above shortcomings (and also add to your current environment variable usage).
Add a config.js
or config.ts
file!
What do I mean?
I mean consolidate access of environment variables from using process.env.XXX_YYY
everywhere, to just accessing it once! Through a single file(s)!
It can look something like
export const Config = {
cmsUrl: process.env.CMS_URL,
dbHost: process.env.DB_HOST,
dbUser: process.env.DB_USER,
dbPassword: process.env.DB_PASSWORD,
dbName: process.env.DB_NAME,
jwtSecret: process.env.ZEROCHASS_SECRET,
awsRegion: process.env.AWS_REGION,
awsBucket: process.env.AWS_BUCKET,
twitterApiKey: process.env.TWITTER_API_KEY,
}
Now, whenever I want to access any of these environment variables, I can do so by importing this file.
No more having to write process.env.MY_VARIABLE
over and over!
My above example with axios becomes this
import { Config } from './config';
// Usage 1
axios({
url: `${Config.cmsUrl}/random-endpoint-1`
header: `Bearer ${Config.twitterApiKey}`
});
// ...
// Usage 2
axios({
url: `${Config.cmsUrl}/random-endpoint-2`
header: `Bearer ${Config.twitterApiKey}`
});
If I ever need to change the environment variable that the Twitter API key was stored in, I don't have to change a zillion files, I just change it here in config.ts
!
If I need to add documentation and group items, I can easily add it here.
export const Config = {
general: {
/** The URL for our Craft environment */
cmsUrl: process.env.NEXT_PUBLIC_CRAFT_CMS_URL,
jwtSecret: process.env.ZEROCHASS_SECRET,
/** The stage we're on, should be QA/Dev/Prod */
nodeEnv: process.env.NODE_ENV,
},
database: {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
name: process.env.DB_NAME,
},
aws: {
region: process.env.AWS_REGION,
bucket: process.env.AWS_BUCKET,
},
twitter: {
/** API v1 URL for Twitter */
apiUrl: process.env.TWITTER_API_URL,
/** API key for our Twitter app */
apiKey: process.env.TWITTER_API_KEY,
},
}
And anyone who imports this file will get all that context, including the code hints on hover!
Hopefully this short post has given you some insight on how you might rethink your environment variable usage. You can even throw in some value-validation here, but I won't cover that here.
Let me know your thoughts!
Top comments (1)
Great post, Chris! I really liked the setup to change variables as the application scales. A thing I didn't notice if you are using or not is the dotenv (dependency to load variables) or what other alternative are you using here. Another really nice concept to explore would be how to share your .env file since this file isn't uploaded anywhere. Perhaps an idea for a future post! Thanks for sharing this!