Environment Variables in NodeJs: The complete Guide

Environment Variables in NodeJs: The complete Guide

·

12 min read

In this article, we are going to cover environment variables also known as env variables. These are basically key-value pair of data set that is stored in the operating system level

In this article we will learn about environment variables in NodeJs with examples

  • What are Environment variables

  • Why are env variables are important

  • Prerequisites for the project

  • Installing NodeJs and setting up a new Project

  • Initializing your first env variable with NodeJS

  • env variables in API calling / async tasks

  • env variables in Database operations / async tasks

  • Advanced env variable manipulation in Node JS: Encoding, Validation and type conversion

  • Secrets management and Security Best Practices with examples

  • Common Pitfalls and how to avoid them

  • Conclusion

Environment Variables

Environment variables are a key-value pair data set that are avaiable at the operating system level. These data set are available in all major operating system command line shells Windows, Mac and Linux

Why are env variable important?

  • Separation of concerns

  • Security

  • Portability

  • Scalability

  • Compatibility

  • Interoperability

Prerequisites for this Project

In this project, we are going to assume the following

  1. Basic knowledge of NodeJs

  2. Basic Knowledge of JavaScript

  3. Some knowledge of Backend development

Installing Node and setting up the project

These are different methods of installing NodeJS on your machine. You can go to the node official website and download a version from there

Let us consider installing nodejs in a linux operating system prefrably ubuntu

Step 1: Open your terminal

Step 2: Use curl or wget script below to install nvm using the nvm git repository. run the script to install the nvm

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
# OR
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash

Step 3: Close and re-open your terminal and run the following to apply the script


export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

Step 4: Confirm Installation

You can confirm that NVM has been installed on your system by running the following command

nvm --version

Step 5 Installing Node

type the below command to install the latest version of node

nvm install latest
nvm install --lts

Step 5 Setting the default version

you can set a default version of node to be used across your system by using the following command

nvm alias default 20.8.0

Step 6: Checking if node is installed

you can type the below command to check if the node is installed on your system

node -v # tells you the current version of node on your system. In our case it is 20.8.0

Initializing the project

Now, that we have installed the node on our machine. Let us create a new project where we will be using the env variable

create a new directory and name it env-demo and cd into the directory

mkdir env-demo
cd env-demo

Now, that we have created the folder and cd into it. Type the below command to initialize a new project and fill out the fields as you see fit

npm init
package name: (env-demo) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC)

this will create a package.json file for you that looks something like this:

{
  "name": "env-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

Initializing your first env variable: Step By Step

Step 1: Install the dotenv package

Now that you have initialized the project in the above section and created a package.json file. In your terminal type the below command to install the node dotenv package that is used to create env files

npm install dotenv --save

This will install the dotenv package and save it as a dependency in your package.json file. that should look something like this

{
  "name": "env-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "dotenv": "^16.3.1"
  }
}

Step 2 Create the .env file

  • In your root folder create a new file and name it .env.

  • Open your .env file in your text editor and create some key-value pairs. These are your environment variable. I have created some for your reference below

DATABASE_URL=sample-database-url
PORT=3000
SECRET_KEY=mysecretkey

Step 3 Load and read the env variables

In your root folder create an index.js file and then open your terminal and type the below command to install express js.

npm install express --save

This installs expressjs and saves it as a dependency in your package.json file, your package,json looks like this

{
  "name": "env-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "dotenv": "^16.3.1",
    "express": "^4.18.2"
  }
}

and your project structure looks like this

Now, open your index.js file and type the below command to start a simple web server.

const express = require('express');
const app = express();
const port = 4000;  // defaults to 4000

app.get('/', (req, res) => {
    res.send('Hello World!')
  })

app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}/`);
});

run the code with

node index.js

and go to localhost://4000 to get the hello world

loading and reading the .env file

Now that we have the server running let us read the .env file that we created in the previous step and load the port details from the .env file

Open the index.js file and require the dotenv library there

require('dotenv').config()

now, you can access the .env file using the process.env. Let us use the process.env to access the port number in our index.js file

const express = require('express');
require('dotenv').config()
const app = express();
const port = process.env.PORT || 4000;  // Read from .env if not available then defaults to 4000

app.get('/', (req, res) => {
    res.send('Hello World!')
  })

app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}/`);
});

Now let us restart the server and go to localhost://3000 . Instead of running at 4000 our server is now running on port 3000 which it took from the .env file

you can also see this in the console

You have now successfully created and saved the env file on your machine

As another example you can access the DATABASE_URL like so

const dbUrl = process.env.DATABASE_URL;
// Use dbUrl to connect to your database

Here are all the files

index.js

const express = require('express');
require('dotenv').config()
const app = express();
const port = process.env.PORT || 4000;  // Read from .env if not available then defaults to 4000

app.get('/', (req, res) => {
    res.send('Hello World!')
  })

app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}/`);
});

package.json

{
  "name": "env-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "dotenv": "^16.3.1",
    "express": "^4.18.2"
  }
}

.env

DATABASE_URL=sample-database-url
PORT=3000
SECRET_KEY=mysecretkey

How to use env variables in async tasks

In this section we are going to discuss about how env variables can be used in async tasks like calling an API or database operations

Env variables are especially important in these tasks, because they can be used to store credentials and endpoints securely

Also, these variables can be automated because these change between different environments such as development, staging and production.

Step 1 Setting up the env variables

open your .env file and edit it like this

API_URL=https://jsonplaceholder.typicode.com/todos/1
API_KEY=sfjks4325
PORT=3000

Step 2 using async / await with env variables

Next, install the axios library to make calls to the remote server like

npm install axios --save

this will install the axios and save it as a dependency in your package.json file

the package.json file should look something like this

{
  "name": "env-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^1.5.1",
    "dotenv": "^16.3.1",
    "express": "^4.18.2"
  }
}

Next use the axios to make calls to the jsonplaceholder website and log them to the console

Type this code in your index.js file

const express = require('express');
require('dotenv').config();
const axios = require('axios');
const app = express();
const port = process.env.PORT || 4000;  // Read from .env if not available then defaults to 4000

const fetchData = async () => {
  const url = process.env.API_URL;
  const apiKey = process.env.API_KEY;

  try {
    const response = await axios.get(url, {
      headers: { 'Authorization': `Bearer ${apiKey}` }
    });
    console.log(response.data);
  } catch (error) {
    console.error(`Error fetching data: ${error}`);
  }
};


app.get('/', (req, res) => {
  fetchData()
    res.send('Hello World!')
  })

app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}/`);
});

What are we doing here:

  1. We are importing the modules like express, dotenv, axios

  2. Then we are initalizing the app and the port using the env variables

  3. then we are creating the async function called const fetchData = async ()=> (...)

  4. In this function we are using the url and the apiKey from the .env file. Though I have to mention you do not need apiKey to call the jsonplaceholder website. I have just put that key there for demo purposes

  5. we are logging the data to the console

  6. We are calling the fetchData() method on the get route. So, everytime someone goes to the / the method gets called and the data gets logged to the console.

Using Async/await with env variables in Database operations

Let us look at another example, this time we are going to do database operations with env variables

step 1 set up the env variables

Open the .env file in your text editor and type the following

DB_HOST=localhost
DB_PORT=5432
DB_NAME=mydatabase
DB_USER=username
DB_PASSWORD=password

Step 2 Install the pg library

next step is to install the pg library like so

npm install pg --save

Step 3 Connect to the database and make the async query with .env file

write the following code to connect to the database and use the .env credentials that we just created

const { Pool } = require('pg');
require('dotenv').config();

const pool = new Pool({
  host: process.env.DB_HOST,
  port: process.env.DB_PORT,
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD
});

const fetchUsers = async () => {
  try {
    const res = await pool.query('SELECT * FROM users');
    console.log(res.rows);
  } catch (err) {
    console.error(`Error fetching users: ${err}`);
  }
};

fetchUsers();

we are not integrating this example into our regular codebase because it is an outlier and will not be useful further down the line in our tutorial

Advanced env variable manipulation in Node JS: Encoding, Validation and type conversion

There is more to env variables than just reading and storing them. With advanced env variable manipulation we are going to learn about

  • Encoding

  • Validation and

  • Type conversion

1. Encoding:

Encoding is used for various purposes in environment variables. This could be used for security puposes. The most popular type of encoding is done using the base64

Encoding

const buffer = Buffer.from('This is a secret', 'utf-8');
const encodedData = buffer.toString('base64');
console.log(`Encoded: ${encodedData}`);

Decoding:

const decodedData = Buffer.from(encodedData, 'base64').toString('utf-8');
console.log(`Decoded: ${decodedData}`);

2. Validation

validation is used to check whether the code is valid or not. for example the url that you are going to use is valid url or not etc

or as in this example we are checking whether the port number is within the specified range or not

const validatePort = (port) => {
  const parsedPort = parseInt(port, 10);
  if (isNaN(parsedPort) || parsedPort < 1024 || parsedPort > 65535) {
    throw new Error('Invalid port number');
  }
  return parsedPort;
};

const port = validatePort(process.env.PORT);

3. Type conversion

env variables are always on the type : string. If often you need to use other data types as well such as int or booleans. Here type conversion helps

Integer example

const isProduction = process.env.NODE_ENV === 'production';
const retries = parseInt(process.env.RETRIES, 10) || 3;

Boolean example

const shouldLog = process.env.SHOULD_LOG === 'true';

Combination example

Here is an example combining all the advanced env manipulation available in the env variables

// Encoding and decoding
const apiKey = Buffer.from(process.env.API_KEY || '', 'base64').toString('utf-8');

// Validation
if (apiKey.length !== 32) {
  throw new Error('Invalid API key length');
}

// Type conversion
const maxUsers = parseInt(process.env.MAX_USERS, 10);
if (isNaN(maxUsers)) {
  throw new Error('MAX_USERS must be an integer');
}

Secrets Management and Security Best Practices with examples

In this section we are going to learn about secrets management and best practices with examples. Here are the steps that you can take to secure the env variable along with their examples

1. Never commit the .env files

Always ensure that the .env files are in the gitignore so they are never commited to the git repository

# Ignore dotenv environment variables file
.env

2. Hashing password

always hash the passwords, storing the passwords as plain text is a bad practice. You can use libraries like bcryt and others to hash the passwords

const bcrypt = require('bcrypt');
const saltRounds = 10;

async function hashPassword(plainPassword) {
  const hash = await bcrypt.hash(plainPassword, saltRounds);
  return hash;
}

3. Encoding the env variables

As a security measure always encode the secrets, the secrets are usually base-64 encoded

const buffer = Buffer.from(process.env.MY_SECRET, 'base64');
const decodedSecret = buffer.toString('utf-8');

4. Centralized secrets management

for large projects you can consider centralized secrets management services like hashicorp vault, AWS secrets manager and others

These offer advanced features like secret rotation, automated lease and rotation and audit logging

here is an example with node vault

const vault = require("node-vault")({
  endpoint: "https://vault-endpoint",
  token: process.env.VAULT_TOKEN,
});

async function getDatabaseCreds() {
  const data = await vault.read("path/to/secret");
  return {
    user: data.data.username,
    password: data.data.password,
  };
}

5. Least privilege principle

Always follow the principle of least privilege. The API which does not require write credentials should never be given write privileges

Common Pitfalls and How to Avoid Them

  1. Hardcoding Sensitive information

  2. Lack of validation

  3. Ignoring Environments

  4. Inadequate security

  5. Poor Documentation

  6. Fallback defaults

Need Chat API for your website or app

DeadSimpleChat is an Chat API provider

  • Add Scalable Chat to your app in minutes

  • 10 Million Online Concurrent users

  • 99.999% Uptime

  • Moderation features

  • 1-1 Chat

  • Group Chat

  • Fully Customizable

  • Chat API and SDK

  • Pre-Built Chat

Conclusion

In this article we learned about env variable and how to use them in NodeJs the complete guide

We have covered the important topics related to env management in NodeJs and this should be enough for most of the use-cases for env variables management in Node js apps

I hope you liked the article and thank you for reading