312

How do I read node environment variables in TypeScript?

If i use process.env.NODE_ENV I have this error :

Property 'NODE_ENV' does not exist on type 'ProcessEnv'

I have installed @types/node but it didn't help.

8
  • what about your tsconfig Commented Jul 19, 2017 at 15:46
  • What version of TypeScript are you using? Since 2.2 this should work. Commented Jul 19, 2017 at 15:59
  • "typescript": "^2.4.1" Commented Jul 21, 2017 at 9:27
  • 1
    Can you find and show us the definition of ProcessEnv that you're using? If it's the DefinitelyTyped definition that @Joe Clay shows, then the dotted notation should work (I can't reproduce your error in 2.4). Commented Jul 21, 2017 at 13:14
  • same thing : export interface ProcessEnv { [key: string]: string | undefined } Commented Jul 21, 2017 at 13:25

22 Answers 22

471

Once you have installed @types/node in your project, you can tell TypeScript exactly what variables are present in your process.env:

environment.d.ts

declare global {
  namespace NodeJS {
    interface ProcessEnv {
      GITHUB_AUTH_TOKEN: string;
      NODE_ENV: 'development' | 'production';
      PORT?: string;
      PWD: string;
    }
  }
}

// If this file has no import/export statements (i.e. is a script)
// convert it into a module by adding an empty export statement.
export {}

Usage:

process.env.GITHUB_AUTH_TOKEN; // $ExpectType string

This method will give you IntelliSense, and it also takes advantage of string literal types.

Note: the snippet above is module augmentation. Files containing module augmentation must be modules (as opposed to scripts). The difference between modules and scripts is that modules have at least one import/export statement.

In order to make TypeScript treat your file as a module, just add one import statement to it. It can be anything. Even export {} will do.

Sign up to request clarification or add additional context in comments.

19 Comments

I get a TS error here, "Augmentations for the global scope can only be directly nested in external modules or ambient module declarations." And this is my react-app-env.d.ts file in a CRA app. For me, removing the declare global and doing declare namespace NodeJS at root worked. Thanks!
That's because augmentations can be made only in modules, not in scripts. The difference is that modules have at least one import/export declaration. To overcome this problem, people tend to add an empty import statement just to make TypeScript treat your file as a module. Something like import * as ts from 'typescript'.
As it turns out, that wasn't needed. I just needed to remove the declare global.
You can also do export {}.
be sure to add the "module.d.ts" to the include parameter in your tsconfig.json file.
|
89

There's no guarantee of what (if any) environment variables are going to be available in a Node process - the NODE_ENV variable is just a convention that was popularised by Express, rather than something built in to Node itself. As such, it wouldn't really make sense for it to be included in the type definitions. Instead, they define process.env like this:

export interface ProcessEnv {
    [key: string]: string | undefined
}

Which means that process.env can be indexed with a string in order to get a string back (or undefined, if the variable isn't set). To fix your error, you'll have to use the index syntax:

let env = process.env["NODE_ENV"];

Alternatively, as jcalz pointed out in the comments, if you're using TypeScript 2.2 or newer, you can access indexable types like the one defined above using the dot syntax - in which case, your code should just work as is.

6 Comments

You may want to mention that TypeScript 2.2 and newer allows indexable types to be be accessed with dotted properties.
@jcalz: Huh, I didn't know that, thanks for the info!
@jcalz I'm using typescript 2.4.1, is there something to do to use this ? the dotted notation didn't work for me.
Piggybacking on the accepted answer here to mention env-var, a module I wrote. it will read variables from process.env and coerce them from string to the correct type you need such as number. With TypeScript 2.2 you obviously don't need it, but it makes working with process.env much cleaner.
@EvanShortiss thanks for mentioning that library. It looks great. I've mentioned it in my answer as well.
|
55

just add before use process.env.NODE_ENV follow lines:

declare var process : {
  env: {
    NODE_ENV: string
  }
}

3 Comments

I don't know why this works, but thank you! I took a more general purpose solution as declare var process: { env: { [key: string]: string; } };
Thanks I put this right above my mysqlConnection = createConnect in my conection.ts and I named each key: type. like this declare var process: { env: { HOST: string; USER: string; PASSWORD: string; DB: string; PORT: number; }; };
@Shakeel this works because of Declaration Merging: typescriptlang.org/docs/handbook/…
52

You can use a Type Assertion for this

Sometimes you’ll end up in a situation where you’ll know more about a value than TypeScript does. Usually this will happen when you know the type of some entity could be more specific than its current type.

Type assertions are a way to tell the compiler “trust me, I know what I’m doing.” A type assertion is like a type cast in other languages, but performs no special checking or restructuring of data. It has no runtime impact, and is used purely by the compiler. TypeScript assumes that you, the programmer, have performed any special checks that you need.

Example

const nodeEnv: string = (process.env.NODE_ENV as string);
console.log(nodeEnv);

Alternatively you might find a library such as env-var more suitable for this specific purpose --

"solution for loading and sanitizing environment variables in node.js with correct typings"

2 Comments

This was the missing part in my case, which solved: TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'. Type 'undefined' is not assignable to type 'string'.
This should really only be done if you asserted that the environment variables are correct before
37

1. Create a .env file

# Contents of .env file
AUTHENTICATION_API_URL="http://localhost:4000/login"
GRAPHQL_API_URL="http://localhost:4000/graphql"

2. Load your .env file into process.env with dotenv

We can leverage dotenv to set environment-specific process.env variables. Create a file called config.ts in your src/ directory and populate as follows:

// Contents of src/config.ts

import {config as configDotenv} from 'dotenv'
import {resolve} from 'path'

switch(process.env.NODE_ENV) {
  case "development":
    console.log("Environment is 'development'")
    configDotenv({
      path: resolve(__dirname, "../.env.development")
    })
    break
  case "test":
    configDotenv({
      path: resolve(__dirname, "../.env.test")
    })
    break
  // Add 'staging' and 'production' cases here as well!
  default:
    throw new Error(`'NODE_ENV' ${process.env.NODE_ENV} is not handled!`)
}

Note: This file needs to get imported in your top-most file, likely your src/index.ts via import './config' (placed before all other imports)

3. Check ENV variables and define IProcessEnv

After combining a few methods above, we can add some runtime checks for sanity to guarantee that our declared IProcessEnv interface reflects what ENV variables are set in our .env.* files. The contents below can also live in src/config.ts

// More content in config.ts
const throwIfNot = function<T, K extends keyof T>(obj: Partial<T>, prop: K, msg?: string): T[K] {
  if(obj[prop] === undefined || obj[prop] === null){
    throw new Error(msg || `Environment is missing variable ${prop}`)
  } else {
    return obj[prop] as T[K]
  }
}
// Validate that we have our expected ENV variables defined!
['AUTHENTICATION_API_URL', 'GRAPHQL_API_URL'].forEach(v => {
  throwIfNot(process.env, v)
})

export interface IProcessEnv {
  AUTHENTICATION_API_URL: string
  GRAPHQL_API_URL: string
}

declare global {
  namespace NodeJS {
    interface ProcessEnv extends IProcessEnv { }
  }
}
   

This will give us proper IntelliSense/tslint type checking, as well as some sanity when deploying to various environments.

Note that this also works for a ReactJS app (as opposed to a NodeJS server app). You can omit Step (2) because this is handled by create-react-app.

2 Comments

unfortunately TS can't automatically recognise that you safe guarded the type with throwIfNot, so the declare global is still required. I still like this approach and went for somethign similar. thanks!
I like that throwIfNot() function as a general purpose utility!
33

After executing with typescript latest version:

npm install -D --save @types/node

you can use process.env directly.

console.log(process.env["NODE_ENV"])

you will see the expected result if you have set NODE_ENV.

3 Comments

This is by far the simplest alternative.
I was trying to find a solution for 2 weeks... Thanks a lot
This seems to be the same suggestion as the accepted answer?
25

what worked for me is that everywhere I want to use process.env I first import dotenv and call config() on it. Also, remember to append ! at the end and ensure the attribute is defined in your .env file

import dotenv from 'dotenv';

dotenv.config();

export const YOUR_ATTRIBUTE = process.env.YOUR_ATTRIBUTE!;

4 Comments

What does this "!" sign actually do?
@VadimSheremetov the ! is used to tell the compiler that the value will not be undefined. For example, the type of a variable may be "string | undefined | null". If you try to assign this variable the compiler will complain that the value could be null or undefined, so by adding the ! you tell the compiler to ignore or remove that check since you have taken the responsibility and will ensure that the value is not undefined. so typescript will not shout at you and you can run you program with ease. Hope this was helpful
Have you considered using process.env in only one place, perhaps a place that exports a configuration object built with process.env, etc? Less duplication of code, less to maintain.
Don't use !. Rather define your types so they are correct.
16

Here is a short function which is guaranteed to pull the process.env value as a string -- or to throw an error otherwise.

For something more powerful (but also bigger), others here have suggested env-var.

/**
 * Returns value stored in environment variable with the given `name`.
 * Throws Error if no such variable or if variable undefined; thus ensuring type-safety.
 * @param name - name of variable to fetch from this process's environment.
 */
export function env(name: string): string {
  const value = process.env[name];

  if (!value) {
    throw new Error(`Missing: process.env['${name}'].`);
  }

  return value;
}

You should then be able to write code like:

let currentEnvironment: string;
currentEnvironment = env('NODE_ENV');

Comments

16

I know this will help someone who searches for this and can't find the simple answer to why your proccess.env variables are making your compiler whine:

Install @types/node:

npm i @types/node

Then where ever you're including your env as a string, do this:

process.env.YOUR_ENV ?? ''

The double question marks allow you to check for null/undefined.

1 Comment

this is not a good solution. It generates the following error message if you apply this trick with the double question mark to more than 1 env variable : OverwriteModelError: Cannot overwrite `` model once compiled
16

create a file like global.d.ts


declare global {
  namespace NodeJS {
    interface ProcessEnv {
      SECRET: string;
    }
  }
}
export {};

tutorial by Christian Höller

3 Comments

it worked pretty well for me abi.
The linked article is blocked by a login wall.
The export {}; was the key. Thanks!
8
  1. Install @types/node by running npm i @types/node
  2. Add "types": [ "node" ] to your tsconfig.json file in the compilerOptions section.

1 Comment

I lost a lot of time until I did step 2. Thanks Muhammad.
3

I found that deliberately changing the path to the .env file was my issue as detailed here: https://stackoverflow.com/a/62288163/3605990

tl;dr

module:

import * as dotenv from "dotenv";
dotenv.config({ path: __dirname+'/.env' });

or commonjs:

require('dotenv').config({ path: __dirname+'/.env' });

Comments

2

Complementing previous responses and after some time with this problem, even installing @types/node, I found this answer. In short, just run a reload window:

"...Although, you probably have to restart typescript language server if it still uses previous version of the tsconfig. In order to do this in VS Code, you do Ctrl+Shift+P and Reload Window or TypeScript: Restart TS server if available..."

Comments

2

here's my solution with envalid (validating and accessing environment variables in Node.js)

import { str, cleanEnv } from 'envalid'

const env = cleanEnv(process.env, {
  clientId: str(),
  clientSecret: str(),
})

// and now the env is validated and no longer undefined
const clientId = env.clientId

Comments

2

Important note: if you have a web app and you are using webpack.DefinePlugin to define process.env on your window, then these are they typings you are looking for:

declare namespace process {
    let env: {
        // this is optional, if you want to allow also
        // other values than the ones listed below, they will have type 
        // string | undefined, which is the default
        [key: string]: string
        commit_hash: string
        build_time: string
        stage: string
        version: string
        // ... etc.
    }
}

Comments

1

Just typecast the process.env.YOUR_VAR

Example:

mongoose
  .connect(String(process.env.MONGO_URL), {
    useNewUrlParser: true,
    useFindAndModify: false
  })
  .then(() => console.log('DB connected'))
  .catch((err: any) => console.error(err));

1 Comment

This will still not work as process is not declared.
1

The best and easiest way to use node process.env in your typescript project is to first compile with tsc then run the compiled javascript file with node supplying your ENV var. Example (first make sure tsconfig.ts is what you want for the output directory also the name of compiled file, I am using dist as output directory and index.js as example):

cd my-typescriptproject
tsc
NODE_ENV=test node ./dist/index.js

Comments

1

For anyone coming here looking for an answer for Create React App projects specifically, your variable names should start with REACT_APP_

Read more here: https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables

2 Comments

only for create-react-app.
We don't use CRA any more.
1
npm i --save-dev @types/node
  • Create at your root environment.d.ts. It will be anyway always type string
export declare global {
    namespace NodeJS {
        interface ProcessEnv {
            [key: string]: string;
        }
    }
}

Comments

0

You could also use a type guard function. Something like this that has a return type of

parameterName is string

e.g.

function isEnvVarSpecified(envVar: string | undefined): envVar is string {
  if(envVar === undefined || envVar === null) {
    return false;
  }
  if(typeof envVar !== 'string'){
    return false;
  }
  return true;
}

You can then call this as a type guard:

function myFunc() {
  if(!isEnvVarSpecified(process.env.SOME_ENV_VAR')){
      throw new Error('process.env.SOME_ENV_VAR not found')
  }
  // From this point on the ts compiler won't complain about 
  // process.env.SOME_ENV_VAR being potentially undefined
}

Comments

0

I wrote a module to simplify this. It has no dependencies so it's reasonably lightweight. It also works with dotenv, and you can pass a custom process.env to the env.from function if you need to.

It's mentioned in a few answers already, but here's an example:

Install it using yarn/npm:

npm install env-var --save

Then read variables:

import * as env from 'env-var'

// Read NODE_ENV and verify that:
// 1) it is set using the required() function
// 2) it is either 'dev' or 'prod'
// 3) throw a runtime exception if conditions #1 or #2 fail
const environment = env.get('NODE_ENV').required().asEnum(['dev', 'prod'])

// Intellisense will suggest 'dev' or 'prod'
if (environment === 'dev') {
  console.log('yep, this is dev')
} else {
  console.log('looks like this is prod')
}

Or another:

import { get } from 'env-var'

// Read the GitHub token. It could be undefined
const githubToken = get('GITHUB_TOKEN').asString()

// Read MAX_CONCURRENCY, or default to 5. Throw an error if it's
// not set to a positive integer value
const concurrencyLimit = get('MAX_CONCURRENCY').default(5).asIntPositive()

function callGitApi (token: string, concurrency: number) { /* implementation */ }

// TS Error: Argument of type 'string | undefined' is not assignable to
// parameter of type 'string'.
callGitApi(githubToken, concurrencyLimit)

4 Comments

I've had trouble with this and vite because process is undefined in vite (which is a vite specific thing wherein they access env vars differently).
Can you try testing this workaround? If successful it can be merged into master github.com/evanshortiss/env-var/issues/155
I ended up using github.com/ElMassimo/vite-plugin-environment which worked really well otherwise I would test that. Thank you for creating it though Evan. Perhaps you could see what ElMassimo does to emulate the relevant aspects of their solution.
For sure, thanks! In v8 of env-var I've added a docs section for React, and will do so for Vite's static replacement of import.meta for variables too. Browser environments are tricky.
0

My way:

declare let process: Omit<NodeJS.Process, 'env'> & {
  env: {
    NODE_ENV: 'development' | 'production';
    // Your variables goes here
  };
};

1 Comment

I don't think overriding built-in declarations is a good idea.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.