How to add Environment Variables in Node.js with Typescript in a type-safe way

How to add Environment Variables in Node.js with Typescript in a type-safe way

Introduction

In this article, I'm going to show you a type-safe way to add environment variables to your Node.js application, with Typescript.

By default, Node.js does not recognize your .env file, so as soon as you start a brand-new application, process.env will contain only variables that were set at the moment the process was started.

How Dotenv works

To access our environment variable, like the commonly used PORT which we always use to define where our app will be available, we need to load them to the process.env , but how to do this easily? The package dotenv does the trick.

If we take a look at its source code, in the main.js file, this is the last step he does:

try {
    // specifying an encoding returns a string instead of a buffer
    const parsed = parse(fs.readFileSync(dotenvPath, { encoding }), { debug })

    Object.keys(parsed).forEach(function (key) {
      if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
        process.env[key] = parsed[key]
      } else if (debug) {
        log(`"${key}" is already defined in \`process.env\` and will not be overwritten`)
      }
    })

    return { parsed }
  } catch (e) {
    return { error: e }
  }

The script will loop through the file which stores your environment variables, parse then, and load each one to a new key inside process.env , with the value described on your .env file (or any other name you choose).

To use dotenv package is pretty simple, just install it with:

yarn dotenv

or

npm install dotenv

And then, call it in the entry point of your application, let's suppose you have a index.ts file at the root of your project:

import express from 'express';
import dotenv from 'dotenv';

dotenv.config();
// your server code here

This line dotenv.config() does all the necessary magic to enable you to access custom environment variables on your app.

Common problems with non-typed env variables

But now we have a problem when using TypeScript, as soon as you pass a variable from your .env file to a 3rd-party library, like Redis, to set up your database configuration, you might face this warning on your IDE:

TS2322: Type 'string | undefined' is not assignable to type 'string'. Type 'undefined' is not assignable to type 'string'.

This means that Node.js doesn't know the content of process.env , so it could be a string or undefined, and the library strictly requires a string.

Solving this problem with ts-assert-exists

To bypass that, we can create a file, usually in a config folder, which exports all of our environment variables, but with the help of another library, which is called ts-assert-exists .

This library is pretty simple, it only receives a parameter (which is the variable from .env), returns the variable, strictly typed as a string, or throws an error if that variable is undefined. That's all, and here is the code if you want to create your implementation:

function assertExists(value, messageToThrow) {
    if (value !== undefined && value !== null) {
        return value;
    }
    else {
        throw new Error(messageToThrow || 'assertExists: The passed value doesn’t exist');
    }
}

So our config file should look like this:

import assertExists from 'ts-assert-exists';

export default {
  PORT: Number(assertExists(process.env.PORT)),
  REDIS_USER: assertExists(process.env.REDIS_USER),
  REDIS_PASSWORD: assertExists(process.env.REDIS_PASSWORD)
};

You can even do some type casting inside this file, so you will always get it with the correct type on any other file you might need your variable.

So now, instead of calling process.env.DB_USER directly, use the config file and access the desired variable, totally type-safe.

And we are done!


Thanks for reading my article, feel free to point out any mistake that I may make.

I hope you guys like it :)