# Handling Environment Variables in Node.js

Environment variable configuration is crucial for effective secret management and following of best practices such as the twelve-factor methodologies.

This is why we recently hosted a masterclass exploring why managing configurations is important, how to distinguish between environments, the risks of using NODE\_ENV, Node.js configuration best practices and more. 

Watch the full masterclass here:

%[https://www.youtube.com/watch?v=kVnh_tNYqxk] 

In this blog, we’ve recapped the key takeaways from this class, focussing specifically on providing an in-depth guide of NODE\_ENV, how to use Fastify secret management plugins, and ultimately how Platformatic natively supports environment variables.

### **The Twelve-Factor Methodology** 

Software development requires engineers to pay close attention to best practices to build web applications in the most secure and scalable way.

Twelve-Factor is a methodology for building modern software, especially Software-as-a-Service (SaaS). This methodology specifically deals with software products' backend development and development operations.

Using the Twelve-Factor methodology in an application offers the following benefits:

* Easy onboarding of new developers, empowered by the use of declarative formats during setup automation 
    
* Seamless relationship of the codebase with the underpinning operating system
    
* Ease of deploying on the cloud
    
* A close relationship between development and production
    
* Application scalability
    

The Twelve-Factor methodology also touches on the subject of configuration, stating that configurations should be stored within the environment.

This subsequently raises the question– what is an environment?

### **What is an environment in software development?**

A development environment is a *digital platform* or a system where engineers can build, test, deploy, and manage software products in production. The process of creating a new software product, also known as the [Software Development Life Cycle](https://www.coursera.org/articles/software-development-life-cycle), occurs within environments.

Conventionally, there are four stages or types of environment: 

* Development
    
* Testing
    
* Staging
    
* Production
    

The development phase is where the product is built from scratch, and the engineers can make changes as may be required. Nowadays, engineering teams utilize version control systems and integrated development environments such as VS Code or Notepad++ during this phase.

The team tests the code to ensure all the functionalities work as expected at the testing phase. Apart from functionalities, the application's security will also be tested to ensure it can withstand threat attacks to a large extent.  
  
Once an application has passed this stage, it moves to the Staging phase. The main difference between the testing and staging phase is that in the latter, the application will be tested within the context of how it works in production.

The staging phase is a *mock-up* of the production. It helps to cross-check everything to correct or improve before pushing to production, where the end-users can access the application and use it in real-time.

### **What are environment variables?**

Environment variables are values that can affect the performance of an application. They are not predefined but are rather defined by the engineer building or maintaining the software, and they often appear in the key-value pair format.

It is encoded as ENV, which is initialized with *process.env*, across a codebase. At execution, the environment processes the actual value of the encoded ENV.

There are three main reasons for environment variables:

* They make it easier to reference and use configuration details across a codebase
    
* Once the configuration details are modified in the config file, it reflects across the application
    
* Security of configuration details, to a certain extent
    

The third reason, security,  is questionable because the container runtime plainly stores environment variables, which makes it vulnerable to data breaches.  
  
So how can you handle this vulnerability? By using secret management systems.

### **Secret Management with Fastify**

Plainly placing your configuration details endangers your application’s security. But with the help of secret managers, your configuration data will be encrypted outside of your infrastructure.

This places extra layers of security on the application’s configuration. This is where Fastify, a popular Node.js framework, comes in as an avenue to use secret managers.  
  
[NearForm](https://www.nearform.com/) created some modules to use Fastify Secret with [AWS](https://github.com/nearform/fastify-secrets-aws), [HashCorp Vault](https://github.com/nearform/fastify-secrets-hashicorp), [Azure Key Vault](https://github.com/nearform/fastify-secrets-azure/blob/master/LICENSE), and [Google Secret Manager](https://github.com/nearform/fastify-secrets-gcp). Here is an example of how to use Fastify with AWS Secret Manager:

```javascript
import Fastify from 'fastify'
import FastifySecrets from 'fastify-secrets-aws'


const fastify = Fastify()


//loads the current user with the AWS SDK


await fastify.register(FastifySecrets, {
    secrets: {
        dbPassword: 'your-secret'
    }
})


// not to be printed
console.log(fastify.secretss.dbPassword)
```

### **Validating Environment Variables**

After inputting configuration details, we must ensure they are correct at runtime. A good way to do so is by validating environment variables.

Various libraries can help handle this, including env-schema, Zod, and Typebox.

For example, env-schema is a library that validates environment variables in line with a pre-defined schema. From another perspective, it helps engineers parse JSON schemas and use the JSON schemas to validate their environment variables.

  
Below is a code example showing how to write env-schema:

```javascript
import envSchema from "env-schema"
import { parseArgs } from 'node:util'
import { hello } from './hello.js'

const schema = {
  type: "object",
  properties: {
    HELLO: {
      type: "string"
    }
  },
  required: ["HELLO"]
}

const config = envSchema({
  schema,
  dotenv: true
})

const parsed = parseArgs({
  args: process.argv.slice(2),
  options: {
    name: {
      type: 'string',
      short: 'n'
    }
  }
})

hello(config, parsed.values.name)
```

A major flaw of env-schema, however, is that it is not type-friendly.

[Typebox](https://github.com/sinclairzx81/typebox) is another alternative for developers who would not want to trade-off type-friendliness when validating environment variables with env-schema.

Below is an example of how to use Typebox with env-schema:

```javascript
import { envSchema } from 'env-schema'
import { Static, Type } from '@sinclair/typebox'

const schema = Type.Object({
  PORT: Type.Number({ default: 3000 })
})

type Schema = Static<typeof schema>

const config = envSchema<Schema>({
  schema
})
```

We imported env-schema and the typebox library. Then declare the type object of the schema and its port number. We made the type static, and later configured the schema.

The third option to validate environment variables is the [Zod package](https://www.npmjs.com/package/zod), which is also type-safe. Here is how to configure Zod and use *ConfigSchema*.

```typescript
import { z } from 'zod'


export const ConfigSchema = z.object({
    HELLO: z.string()
})


export type Config = z.infer<typeof ConfigSchema>
```

## **What is NODE\_ENV?**

NODE\_ENV is an environment variable that is used to configure a Node.js application. It can be a key, value, or both. 

Security is crucial in software development, and certain data must not  be exposed in order to avoid the application being compromised. This data can be an API key or a private key to a wallet.

This is why Node.js has a core module called *process*, which in turn has a property named *env*. This module handles NODE\_ENV for applicational configuration and usage.

At production or when using version controls, engineers will often  this environment variable to *gitignore* so its plain values won’t be available for security reasons.

In other cases, some  engineers will set their NODE\_ENV to staging, or production.

Setting NODE\_ENV to various stages of development is, however, a recipe for disaster. So what is the best way to handle this?

### **Set NODE\_ENV=Production For All Environments**

The main idea behind the 4 stages of development is to be as close as possible to production to catch bugs. By setting NODE\_ENV=staging, it takes it farther away from production.

Therefore, it is counter-productive to set NODE\_ENV to staging. A better alternative is to enable feature by feature. Here is an example for better understanding.

```javascript
if (process.env.MY_AWESOME_FEATURE === 'true') {
    // enable feature by feature
}
```

In the snippet above, we are not environment-specific but rather more specific about the feature we want to enable.

## **Demo: Node Configuration with Platformatic**

Platformatic has built-in support for environment variables. You can use Platformatic Service for this purpose, follow these steps:

### **Set Up Your Project**

Start by setting up your project. If you haven't already, create a new Platformatic Service Project using the command:

```plaintext
npm create platformatic@latest
```

You will be prompted with a series of questions. Answer them as follows:

```plaintext
Hello User, welcome to Platformatic 1.16.0!
 Let's start by creating a new project.
? Which kind of project do you want to create? Service
? Where would you like to create your project? .
[18:33:10] WARN: Directory C:\Users\USER\plt is not empty. Some files may be overwritten without confirmation.
? Confirm you want to use C:\Users\USER\plt directory? yes
? Do you want to use TypeScript? no
? What port do you want to use? 3042
? Do you want to create the github action to deploy this application to Platformatic Cloud? no
? Do you want to enable PR Previews in your application? no
? Do you want to init the git repository? no

✔ Installing dependencies...

All done! Please open the project directory and check the README.

To start your application run 'npm start'.
```

### **Start the Application**

To start your application, run:

```plaintext
npm start
```

This will fire up your server on **port 3042**.

![](https://lh7-us.googleusercontent.com/wxW3x384tLojH9NgwV9pzPnp8wTN0kYru2xeSkQxNds2pep1fuXV2cxQPPr_khrd9-Voo6dlGgX8VQFjIwoBApGO5nu5W2jjUy1tu4QFr-F-cVsrbcR1r0cQItDXIZWMB8KCCFqJCbrAqDY9pwFsm3Y align="left")

![](https://lh7-us.googleusercontent.com/WPIzY2YdMs7vfGkMgxtBk-KvJ9BTKVEJulmIJlfASC2VjZehc11K5-wK7AcEMsNocLvXv8PAgnpvaQ3cflilVDqgdg49XaVqaG9XvGkCC8iPo179GaacOLvZsiPGnLVCp6ealtgg7K1JV6hvNA2sHR4 align="left")

### **Test the Application**

To test your application, make a **GET** request to the **/example** route. Access the **OpenAPI documentation interface** in your browser at “[http://127.0.0.1:3042/documentation#tag/default/”](http://127.0.0.1:3042/documentation#tag/default/)

![](https://lh7-us.googleusercontent.com/coR_-Nv8rXNd_bS39ckYYnGNzzpg1OoXEvlaR78Gzc5XJrhlLZe1za9B-7P4UPFf7W9ECqc261qfTuMBhBgOcCowfq-8z9x0lIL7OKgudfK8QdK6VrQIHVhgdFd695qfZaVheLeIdzGN00cyD-JOrfg align="left")

### **Add an Environment Variable**

In your project structure, navigate to the **.env** file and add:

```plaintext
PLT_GREETING=hello
```

So that your .env file looks like this

![](https://lh7-us.googleusercontent.com/urCXYYutoAr9D7YHukyBZr2K2ob2VHSZRmRQl943ZxCqNDSYc9MJzxQa24FMaJYusvhn6KRQ4q9hN1rjkdIE-RMhJ_zPqw0KS9vAkr8RIRPMe6oa4LQGdO011hFui0gbbos6Q6_NGNveto3snPjwTo0 align="left")

### **Update Configuration**

Modify your **platformatic.service.json** configuration file to include the **greeting** option under the **paths** object:

```json
"options": {
          "greeting": "{PLT_GREETING}"
        }
```

So that your **platformatic.service.json** file looks like this 

```json
{
  "$schema": "https://platformatic.dev/schemas/v1.13.1/service",
  "service": {
    "openapi": true
  },
  "watch": true,
  "plugins": {
    "paths": [
      {
        "path": "./plugins",
        "encapsulate": false,
        "options": {
          "greeting": "{PLT_GREETING}"
        }
      },
      "./routes"
    ]
  },
  "server": {
    "hostname": "{PLT_SERVER_HOSTNAME}",
    "port": "{PORT}",
    "logger": {
      "level": "{PLT_SERVER_LOGGER_LEVEL}"
    }
  }
}
```

### **Create a Plugin**

Locate the plugins folder:

![](https://lh7-us.googleusercontent.com/8dzcpbvdDnzOdIOSgl6KhgMrQ7lT7sf2XqwMC38eBDdwj_Rzg6VO3sWtN5ICZMtI795mymd2NMz5yolL_wU3xJLaMQrwd_2YgRvWBdBcRdclIDi_9JqAfc4uYQi2tyleRKyCKrSVh_DyFoTVjQ9oqaI align="left")

Create a new file called **greeting.js** and add the following code:

```javascript
/// <reference path="../global.d.ts" />
"use strict";

module.exports = async function (fastify, opts) {
  fastify.decorate("sayHello", function sayHello(name) {
    return `${opts.greeting} ${name}!`;
  });
};
```

This code creates a **sayHello** function which takes one argument of the **name**. It also uses the greeting environment variable.

### **Add a New Route**

Go to the routes folder:

![](https://lh7-us.googleusercontent.com/Ze9uLaw4R_2faEtmExz1g0GoWhov7MqyGptH3ZZv1KpJRGetSL0m1mXntfeqVTR8DF0zteMZIU7lx6qB7Va86Xr87sVMNu_x39SABFRQ6AVHYiyg4ogcH9Rub5eS9EaTVMLQMbqHAd2YhWwuQutCKJw align="left")

Create a file **hello.js** with the following content:

```javascript
/// <reference path="../global.d.ts" />
"use strict";

module.exports = async function (fastify, opts) {
  fastify.get("/hello", {
    schema: {
      querystring: {
        type: "object",
        properties: {
          name: { type: "string" },
        },
        required: ["name"],
      },
    },
    async (request, reply) => {
      return fastify.sayHello(request.query.name);
    }
  });
};
```

This route listens to /**hello** and expects a **name** query parameter.

### **Test the New Route**

Save the files to restart the server. A new route **/hello** is available, and you can make a GET request to it:

![](https://lh7-us.googleusercontent.com/cpAFz-70mnh5hd_eVUPgi8bK7yWje_cNDoMNsG4yiQlaP4YzsMnNGY-P_0o0CF6R0S0yRmyk__F5nza4P1wRjki_qCV4PS0ofj02dDC-RmvO_yA8DoFSca_kh7jLHtyNRrpJXowPWCiqdkUb4juJs9g align="left")

![](https://lh7-us.googleusercontent.com/SihQ83AJvczgjCFjr1Fjug17Vjp121eFuFHxqGcXTGHCogcYFw27p6sYhq8W7AaGRss04fHVyV8LO2QA6a5qBHyxcMfN_pN8NC7F8gqUsLIK_L4jh54TEZKclNcp6uGAxqfwrIwXNuvt2Kx1AnNwAxM align="left")

You can also make a **cURL** request on your terminal using this command:

```plaintext
$ curl 'http://127.0.0.1:3042/hello?name=John
```

This is the expected result as seen in the terminal:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705926774064/82b718de-5913-42d2-8c3d-1be1584341e9.png align="center")

## **Wrapping Up** 

In this blog, we have examined the subject of environment variables in Node.js applications, including the best practices.

By now, you should be more aware of how to manage the configuration secrets of your software for better security and performance.

Want to continue learning about Node.js? [Sign up](https://platformatic.dev/events/20240125163000masterclass/) for our next masterclass where we will be exploring how to build modern Node.js & Fastify backends.
