DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Vikas Raj
Vikas Raj

Posted on • Originally published at vikasraj.dev

Environment variables in Node.js. The Right way!

This is my first blog post. Hope y'all like it 🀞.

Environment variables are very fundamental part of developing with Node.js or any server side language. They always contains very sensitive data and doesn't meant to be shared with outside world. You have to make sure that your server is properly configured to make use of correct variables for both development and production environments. Any mistake can lead your server to crash.

Working with environment variable in Node.js is very easy and simple. This post will walk you through the different approaches of using environment variables in Node.js.

If you want to learn about environment variables in Node.js in click here.

1. Using package.json

You are thinking how? But you can pass key=value pairs directly in the npm scripts in the package.json. This is a valid a place to put environment variable but not a secure place.

Below is an example of setting Node.js execution environment to production on the npm's start script.

NOTE: This will probably won't work with Windows OS.

{
    ...
    "scripts": {
        "start": "NODE_ENV=production node bin/www.js"
    }
    ...
}
Enter fullscreen mode Exit fullscreen mode

I also use this approach when I work with debug module or setting the Node.js execution environemnt. Some points to be considered when using this approach:

  • Shouldn't not put any sensitive data in here as it is visible to everyone and you can't ignore package.json in the .gitignore.
  • Don't put more than 2 variables as it could quickly becomes a mess.
  • Separating variables for development and production environment can be very hard.

2. Different keys files

This approach is completely different from first approach and addresses some of the issue of first approach.

Instead of using package.json, we can use keys.dev.js for development and keys.prod.js for production environment. Each file stores different variables for different environment.


Make sure to ignore keys.dev.js in the .gitignore before commiting any changes.


But how we can use them?

Using them can be tricky (when i initially got frustrated), So pay attention. Both files are exported by a third file i.e keys.js which checks the Node.js execution environement i.e. NODE_ENV and exports the correct keys file.

But how can I check Node.js environment before running our code? Look at the first approch for a brief. Below is an example for this approach:

// keys.dev.js ==========
module.exports = {
    PORT: 5000,
};

// keys.prod.js ==========
module.exports = {
    PORT: process.env.PORT,
};

// keys.js ==========
const devKeys = require("keys.dev.js");
const prodKeys = require("keys.prod.js");

if (process.env.NODE_ENV === "production") {
    module.exports = prodKeys;
} else {
    module.exports = devKeys;
}
Enter fullscreen mode Exit fullscreen mode

This approach addresses all the issues of the first approach.

  • Multiple variables can be easily managed.
  • Development and production both have their own keys.
  • Development keys i.e. keys.dev.js can be ignored in the .gitignore keeping secrets away from others.

But no one wants to maintain extra code/file (including me). There must be a better way to do this!

3. .env comes to the rescue

.env is a special file which is used to define environment variables in Node.js. It holds key=value pairs to define the variables.


Make sure to ignore .env in the .gitignore before commiting any changes.


But, Node.js doesn't know how to read and parse this file. How do we do that? Either you could write your own logic to read and parse the file or Use a third party module to do the heavy lifting.

One popular module is dotenv (which i use) which can guide through the basic of .env file.

~ Creating the file

First create a file with name .env in the root of the project which contains all variable which will be injected in the environment by the dotenv.

# .env ======
PORT=5000
WHO_AM_I="Who Knows"
Enter fullscreen mode Exit fullscreen mode

~ Configuring the dotenv

First intall the dotenv package from the npm as a dev dependencies as we don't need this in production.

npm i -D dotenv
Enter fullscreen mode Exit fullscreen mode

There are several methods to load dotenv package. But, I will show you the method that i like.

To load the dotenv package and correctly read the .env file you have to modify the scripts in the package.json. Like below

{
    ...
    "scripts": {
        "start": "node bin/www.js",
        "dev": "node -r dotenv/config bin/www.js"
        // For nodemon users ====
        // "dev": "nodemon -r dotenv/config bin/www.js"
    }
    ...
}
Enter fullscreen mode Exit fullscreen mode

As you can see there are two scripts

  • start for the production
  • dev for the development which loads the dotenv module

This will make sure that we don't accidentally load the dotenv in production.

~ Run the code

Now you can run the server but typing the following command.

npm run dev
Enter fullscreen mode Exit fullscreen mode

And BOOM! You can now use all the variables defined in the .env file by the following syntax.

process.env.YOUR_VARIABLE_NAME;
Enter fullscreen mode Exit fullscreen mode

So What kind of magic is this? What is going on? In the dev script we are telling node to preload a module by passing -r <module_name> flag. By requiring dotenv/config which read and parse the .env and sets the variable in the environment and provide access to those variable before running our code.

Now, we have a single place to define all the environment variables.

To make life a little easier, you can make a separate file i.e keys.js which exports all those variable like so we do in the second approach. This helps us to organize all the variables we use in our code.

NOTE: If you add variables in the .env then also update your exports in keys.js file.

// keys.js ======
module.exports = {
    PORT: process.env.PORT,
    WHO_AM_I: process.env.WHO_AM_I,
};
Enter fullscreen mode Exit fullscreen mode

Points to be considered

  • Always ignore your development keys in the .gitignore.
  • Don't mess with NODE_ENV variable. Values other than development or production can break your app.
  • Always restart your app after changing environment variables.

Top comments (18)

Collapse
 
thesuhu profile image
The Suhu

why your script start prodouction did not load dotenv?

Collapse
 
numtostr profile image
Vikas Raj

dotenv is a dev dependency. In production, keys are stored on the server which can be accessed by node without using dotenv

Collapse
 
safinghoghabori profile image
Safin Ghoghabori

Yes thats right that we store keys on server.
But what about the dotenv package we imported into file and written as process.end.VAR_NAME ? Wont it require to use dotenv package?

Collapse
 
thesuhu profile image
The Suhu

can you give me little example how storing keys on production? thank you

Thread Thread
 
numtostr profile image
Vikas Raj

For example, heroku has a option in app settings to enter environment variable.

Thread Thread
 
thesuhu profile image
The Suhu

yes, in heroku there is an option to store that. but how if we use own server?

Thread Thread
 
numtostr profile image
Vikas Raj • Edited on

Like this

// keys.js ======
module.exports = {
    PORT: process.env.PORT,
    WHO_AM_I: process.env.WHO_AM_I,
};
Thread Thread
 
thesuhu profile image
The Suhu

ok, thank you.

Thread Thread
 
yogendra3236 profile image
Yogendra

But, still we're using 'process.env', which uses to 'dotenv' package in production?

Thread Thread
 
thesuhu profile image
The Suhu

In production I didn't use .env, I store all credentials on host environment or if I use docker, I store it in docker secrets.

Thread Thread
 
yogendra3236 profile image
Yogendra

Cool, thanks!

Collapse
 
chrissyast profile image
chrissyast

My script is currently

"serve": "vue-cli-service serve"

I tried adding "node -r dotenv/config vue-cli-service serve"

I ran that but then it failed to compile

internal/modules/cjs/loader.js:796
    throw err;
    ^

Error: Cannot find module './front/vue-cli-service'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:793:17)
    at Function.Module._load (internal/modules/cjs/loader.js:686:27)
    at Function.Module.runMain (internal/modules/cjs/loader.js:1043:10)
    at internal/main/run_main_module.js:17:11 {
  code: 'MODULE_NOT_FOUND',
  requireStack: []
}
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! front@0.1.0 serve: `node -r dotenv/config vue-cli-service serve`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the front@0.1.0 serve script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

Collapse
 
johnchristopher profile image
johnchristopher

Another option is to use command line arguments like node app.js --option argument which prevents any keys to ever be committed or written to a file since they are only made available at run-time.

It also plays nice with docker since one can just write any secrets into a docker .env file that feeds ENV variables either to a container or an image.

Collapse
 
espinella8 profile image
espinella8

If I used the same server for development and production, I'd have to not install dotenv as a dev dependency and not use server variables, just 2 different .env files, one for each prod/dev application folder.

Collapse
 
ovidiu141 profile image
Ovidiu Miu • Edited on

Ok, but how to manage the .env files? Where to keep them? How to share them? For example how do I share a .env.development file with another developer since the file is not in the source control?

Collapse
 
numtostr profile image
Vikas Raj

You can include .env.development in the source control. But make sure It doesn't contain any actual keys. Because thats the whole point of secret.

The way I do is I make a .env.sample file with all the env but without any secret or api keys.

Collapse
 
mi1682516 profile image
mi1682516

What do you mean when you say it doesn't contain actual keys? How did the other developer run test like i did on my local when it doesn't contain actual keys?

Collapse
 
chan_austria777 profile image
chan πŸ€–

great approach. How do i add a npm script for another environment (i.e, staging environment)?

An Animated Guide to Node.js Event Lop

Node.js doesn’t stop from running other operations because of Libuv, a C++ library responsible for the event loop and asynchronously handling tasks such as network requests, DNS resolution, file system operations, data encryption, etc.

What happens under the hood when Node.js works on tasks such as database queries? We will explore it by following this piece of code step by step.