You are reading environment variables the wrong way in Next.js
Austin Shelby
Posted on October 18, 2021
If you have ever written code that looks like this:
const url = `https://www.example.com/api/blog?api_key=${process.env.API_KEY}`
Then you are doing it wrong!
Here's why this is a bad idea.
In a scenario where you build the application without having set the API_KEY
environment variable the application will use undefined
instead.
Obviously undefined
is not the correct api key which will make any request using that URL fail.
The problem here is that when the error surfaces, the message will be very misleading and look something like this:
Error: Unauthorized
And this error will only show up when you try to use the url to fetch the blog posts.
If fetching the blog posts is an essential feature, the application should not have even compiled without the api key being available.
Naively expecting the API_KEY
environment variable to exist will hide the bug and make this problem a pain to debug due to the misleading error message.
To fix this issue we need two things.
- When a problem exists that causes the application to not function, the application needs to fail immediately and visibly.
- A meaningful abstraction to encapsulate the loading of environment variables.
How to load environment variables in Next.js
This works with any node.js application. Next.js just makes this easier, as it comes with a lot of necessary boilerplate code.
Let me show you how to use environment variables in Next.js correctly, and then explain why this works.
Create a .env.local
file. Here you will put all of your environment variables you want to use on your local development environment.
API_KEY=secret
Next.js automatically adds this file to .gitignore
so you don't have to worry about it ending up in your version control system.
If you are using any other framework than Next.js you need to use a package like dotenv to read the environment variables from a file.
Now to the bread and butter.
Create a config.ts
file with this code to read the environment variables into your config.
const getEnvironmentVariable = (environmentVariable: string): string => {
const unvalidatedEnvironmentVariable = process.env[environmentVariable];
if (!unvalidatedEnvironmentVariable) {
throw new Error(
`Couldn't find environment variable: ${environmentVariable}`
);
} else {
return unvalidatedEnvironmentVariable;
}
};
export const config = {
apiKey: getEnvironmentVariable("API_KEY")
};
And change code that we wrote earlier into this:
import { config } from "./config"
const url = `https://www.example.com/api/blog?api_key=${config.apiKey}`
Why this is the correct way to load environment variables
In a case where you forgot to add the environment variable API_KEY
the application won't even build/compile, and it will throw an error like this: Couldn't find environment variable: API_KEY
.
Our application now fails immediately and visibly.
This is called failing fast.
It is part of the clean code principles, which you can read more about here: https://www.martinfowler.com/ieeeSoftware/failFast.pdf
Because we are using TypeScript, we can be 100% sure that all the values in the config exist.
Additionally, TypeScript helps us avoid small bugs.
If we make a typo:
const url = `https://www.example.com/api/blog?api_key=${config.apiKeu}`
TypeScript will give us the following error:
Property 'apiKeu' does not exist on type '{ apiKey: string; }'. Did you mean 'apiKey'?
How cool is that!
It's like coding with superpowers.
Encapsulating logic
Let's look at the example we started with:
const url = `https://www.example.com/api/blog?api_key=${process.env.API_KEY}`
Do you notice that process.env
part there?
Why should the functionality of fetching blog posts know anything about the user environment the application is currently running in?
Well it shouldn't.
The logic of fetching blog posts doesn't care where it gets the api key from. If it comes from the user environment, text file, or an API doesn't make any difference to it.
Therefore, it shouldn't rely on process.env
or any other low-level abstractions.
Creating a config for the sole purpose of reading environment variables encapsulates this functionality and creates a meaningful high-level abstraction.
A config.
Thanks to this, we can change the way we get the config values (like the api key) without touching the blog post functionality at all!
Another very hidden benefit is that unit testing just became ten times easier. Instead of playing around with our user environment, we can just mock the config with the values we want to.
Conclusion
While this might seem pedantic, keeping these small things in your mind while writing code will make you a better software engineer.
Posted on October 18, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.