You are reading environment variables the wrong way in Next.js
Tags: javascript, webdev, react, nextjs
Published: 2021-10-18 12:38:54

If you have ever written code that looks like this:

```ts
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.

1. When a problem exists that causes the application to not function, the application needs to fail immediately and visibly.
2. 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](https://www.npmjs.com/package/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.

```ts
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:

```ts
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:

```ts
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:

```ts
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.