How to Hide API Keys with Node JS - Environment Variables & .env
How to Hide API Keys with Node JS - Environment Variables & .env

In this post I want to show you how to use environment variables inside NodeJS and why it is important.

Actually really often I see a problem that some data from repositories were leaked in the internet. For example through pushing some code to a public repository on the Github. What does it mean? You just implemented some project and you just want to store it somewhere and you deploy it to Github. But you used there some information that must not be committed. For example an API key to AWS.

So somebody can just go to your repository, find your API key and use it to make their own requests. Which actually means some day you will simply get a letter from AWS that you need to pay a lot of money. And this is happening really often and I know a lot of people who did exactly that. This is why we need to talk about environment variables and how they can help us here.

It's important to remember. You should never write any sensitive information inside your code.

Which actually means if you have some API keys, hosts, ports, database credentials than this information should never be committed even to private repository with your code.

But how we can still use all this sensitive information if we won't commit it? The solution is really simple and it is called environment variables. Actually we simply have some variables inside our environment. For example we are in development and we work on some application locally. We create an additional file with variables inside. And all this variables are sensitive. Host, port, API credentials and much more.

The most important part that file with environment variables MUST be ignored. In this case we can't leak this information because we don't push it anywhere.

So file with environment variables is situated only on our local machine or staging and production server.

Dotenv

The most popular solution in Javascript world for environment variables is called Dotenv package. Dotenv is a zero dependency module that loads environment variables from a .env file into process.env. It means we install this package, write some properties inside .env file and they will be available inside process.env.

Let's check how it works on the real example. I have an empty server.js file and I install 2 modules in my package.json - axios to fetch data from API and express to make a web server.

To have an API key we will use a service https://randommer.io where you can register, get API key and make some requests to get random data from API. Of course in production application you will have some paid solution like AWS which will provide you an API key but to understand the logic free API is totally fine and it is still sensitive information that we want to hide.

const express = require("express");
const app = express();

app.get("/", (req, res) => {
  res.send("Hello API");
});


app.listen(3000, () => {
  console.log("started");
});

Here we just created an express server and started it. For home url we sent a string to understand that our API is working. Now let's try to fetch some data in other url.

...

const apiUrl = "https://randommer.io/api/Name?nameType=fullname&quantity=20";

...

app.get("/names", async (req, res, next) => {
  const result = await axios.get(apiUrl, {
    headers: {
      "X-Api-Key": '5325325yygdfgg7532578ghtrhtr782532',
    },
  });
  res.json(result.data);
});

So here we first created an apiUrl property which returns us a list of random full names. We didn't attach any API key in url as it must be provided inside headers. Now we created a /names route where inside we fetch data from this url using axios. Inside headers we provided a property X-Api-Key which is mandatory to fetch data. You can get this key in your profile after registration.

After we restart our web server and jump to /names we can see a list of fetched full names.

Names response

The main problem with this code is that we will push this API key (which is our sensitive information) to public repository. Then anybody can steal this API key and do other requests. And this is obviously not what we want this is why we want to install Dotenv package.

yarn add dotenv

Now this package is install but we must require it in our code as close to the start as possible.

require("dotenv").config();

const express = require("express");
const app = express();

...

This line with require('dotenv') will load the config to our process.env. Our next step is to create .env file.

RANDOMER_API_TOKEN=5325325yygdfgg7532578ghtrhtr782532

Don't forget that inside .env you don't need spaces but just equal sign between key and value. Now let's check if we get this token inside process.env

console.log(process.env.RANDOMER_API_TOKEN)

After restarting our webserver we can see it directly inside console.

The next step is crucial! We must add .env file to .gitignore. Only in this case we won't commit it.

// .gitignore
.env

Now we can safely use process.env.RANDOMER_API_TOKEN inside our application and nothing will be leaked.

const result = await axios.get(apiUrl, {
  headers: {
    "X-Api-Key": process.env.RANDOMER_API_TOKEN,
  },
});

As you can see everything is working as previously and we can fetch data from our API. But now we store our API key inside environment variables.

But here is a problem. What will happen on the new machine? We have this server.js and new developer won't have this .env file at all. It means that he doesn't know that he must create .env file and he don't know what string should be there. This is why it's a good idea to throw an error if API key is not available.

app.get("/names", async (req, res, next) => {
  try {
    if (!process.env.RANDOMER_API_TOKEN) {
      throw new Error("You forgot to set RANDOMER_API_TOKEN");
    }
    const result = await axios.get(apiUrl, {
      headers: {
        "X-Api-Key": process.env.RANDOMER_API_TOKEN,
      },
    });
    res.json(result.data);
  } catch (err) {
    next(err);
  }
});

We used next to pass our error to Express server and it will be rendered on the screen. We wrapped our code with try catch and throw an error is RANDOMER_API_TOKEN was not found. Now if we remove API key from .env file and try to fetch data we will get a nice error.

Api token error

Frontend security

Here is one more super important point. We build lot's of frontend applications nowadays. Which means we bundle all our Javascript together in a single production file. And what you should never do is write some sensitive information inside your frontend Javascript because it will get to that bundle. If you write frontend code it is exposed by default. Anybody can simply download your bundle and take that API key from it.

So environment variables are super important to hide our sensitive information. Also if you want to learn how to write clean code in Javascript make sure to check this post also.

📚 Source code of what we've done