253

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.

Christophe Le Besnerais
  • 3,895
  • 3
  • 24
  • 44
  • what about your tsconfig – SkyAo Jul 19 '17 at 15:46
  • What version of TypeScript are you using? Since 2.2 this should [work](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#dotted-property-for-types-with-string-index-signatures). – jcalz Jul 19 '17 at 15:59
  • "typescript": "^2.4.1" – Christophe Le Besnerais Jul 21 '17 at 09: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). – jcalz Jul 21 '17 at 13:14
  • same thing : export interface ProcessEnv { [key: string]: string | undefined } – Christophe Le Besnerais Jul 21 '17 at 13:25
  • The only explanation that makes any sense is that you are mistaken about either the version of the TypeScript compiler you're using on your code or the version of the Node typings that the compiler is using to type check. Are you *absolutely sure* that the versions you're checking are what your compiler is actually using? – jcalz Jul 21 '17 at 14:21
  • I'm confused. Should the @types/node version in my package.json match my node version ? This is what I use in my package.json : "@types/node": "^8.0.14", "typescript": "^2.4.1" and my node version is 6.10.2 – Christophe Le Besnerais Jul 21 '17 at 15:00
  • If anyone is using snowpack, you can find solutions here: https://www.snowpack.dev/reference/environment-variables – Spikatrix Nov 27 '21 at 11:58

20 Answers20

377

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.

Karol Majewski
  • 23,596
  • 8
  • 44
  • 53
  • 7
    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! – jedmao Jan 24 '19 at 20:28
  • 6
    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'`. – Karol Majewski Jan 24 '19 at 20:46
  • 6
    As it turns out, that wasn't needed. I just needed to remove the `declare global`. – jedmao Jan 24 '19 at 23:20
  • 2
    Yes — it will work with the current typings for Node.js, because they declare the `NodeJS` namespace as global. But not every library does that, so I thought it's better to suggest the universal solution. – Karol Majewski Jan 25 '19 at 00:15
  • 1
    You can put this in an already existing file `react-app-env.d.ts` – Bilow Oct 12 '19 at 13:19
  • To avoid linter errors of unused variables, you can just to `import 'typescript'` instead of naming some member to import. Thanks for the answer, cheers! – rjhilgefort Nov 25 '19 at 22:10
  • 6
    You can also do `export {}`. – Karol Majewski Nov 25 '19 at 22:35
  • 1
    Excellent answer. I wish this was shown at the very top of the answers section. – Nick Painter Jan 22 '20 at 20:01
  • Hi and thanks for this answer ! Based on the comments here - how should the answer above be modified? – prestonsmith Apr 16 '20 at 14:07
  • 1
    @prestonsmith Edited. Please let me know if that makes sense to you. – Karol Majewski Apr 16 '20 at 20:07
  • 1
    Thanks @KarolMajewski! That's a huge help. I understood the gist of your and @jedmao 's comments but didn't exactly know how to implement them :-) – prestonsmith Apr 16 '20 at 22:41
  • @KarolMajewski I see what you mean now. I restored `declare global` and added `export {}` and this solved my problem today! – jedmao May 02 '20 at 04:02
  • I finally figured out I could reference `NodeJS.ProcessEnv` in a file - otherwise there's no obvious way to import it. – TrueWill May 12 '20 at 20:32
  • Great answer, but shouldn't the PORT be a number? – dsax7 Nov 07 '20 at 08:13
  • 5
    be sure to add the "module.d.ts" to the include parameter in your tsconfig.json file. – Spencer5051 Apr 01 '22 at 04:28
  • @filtfilt the type `ProcessEnv` in NodeJS only is of `string | undefined` (see the answer below of @joe-clay). As a consequence you can't assign other types than strings. – alexanderdavide Jun 07 '22 at 08:44
  • Ever after adding types node https://stackoverflow.com/questions/53529521/typescript-error-cannot-find-name-process was still get time runtime errors in console log with Angular app; So finally ditched this and went with file based way https://www.digitalocean.com/community/tutorials/angular-environment-variables – Alex Punnen Oct 03 '22 at 09:41
  • I needed `export interface ProcessEnv` for this to work. – SebastianR Jan 27 '23 at 18:14
  • Ahhhhh so I was missing the `export {}`!! Thanks a bunch! Others, please pay attention to the export, or it will not work. – Gogol Apr 02 '23 at 10:55
77

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.

Joe Clay
  • 33,401
  • 4
  • 85
  • 85
  • 1
    You may want to mention that TypeScript 2.2 and newer allows indexable types to be be accessed with dotted properties. – jcalz Jul 19 '17 at 16:00
  • @jcalz: Huh, I didn't know that, thanks for the info! – Joe Clay Jul 20 '17 at 11:48
  • @jcalz I'm using typescript 2.4.1, is there something to do to use this ? the dotted notation didn't work for me. – Christophe Le Besnerais Jul 21 '17 at 07:19
  • 2
    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. – Evan Shortiss May 23 '19 at 23:50
  • 1
    @EvanShortiss thanks for mentioning that library. It looks great. I've mentioned it in my answer as well. – Peter W Sep 01 '19 at 09:01
  • my issue was that dotenv was never called – A.com Jun 07 '21 at 13:53
48

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

declare var process : {
  env: {
    NODE_ENV: string
  }
}
haiflive
  • 1,411
  • 13
  • 13
  • 5
    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; } };` – Shakeel Nov 06 '18 at 22:42
  • 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; }; };` – Curtis M May 15 '20 at 22:33
  • 1
    @Shakeel this works because of Declaration Merging: https://www.typescriptlang.org/docs/handbook/declaration-merging.html#global-augmentation – Federico Jan 15 '21 at 13:52
46

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"

Ville
  • 1,182
  • 10
  • 16
  • 1
    This is the best answer. Thanks! – Vincent Wen Aug 20 '21 at 07:46
  • 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'.` – iaforek Dec 09 '21 at 09:32
  • This should really only be done if you asserted that the environment variables are correct before – sezanzeb Jun 21 '23 at 11:58
33

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.

Nickofthyme
  • 3,032
  • 23
  • 40
The Aelfinn
  • 13,649
  • 2
  • 54
  • 45
  • 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! – Doug Sep 21 '20 at 01:31
  • I like that `throwIfNot()` function as a general purpose utility! – Joe Coder Jan 24 '22 at 17:34
29

After executing with typescript latest version:

npm install --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.

pwxcoo
  • 2,903
  • 2
  • 15
  • 21
20

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!;
Erycoking
  • 459
  • 9
  • 15
  • 3
    What does this "!" sign actually do? – Vadim Sheremetov Aug 18 '20 at 10:44
  • 4
    @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 – Erycoking Aug 19 '20 at 23:42
  • 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. – Lee Goddard Jun 13 '22 at 08:05
15

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');
Peter W
  • 952
  • 8
  • 18
13

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.

David Kamer
  • 2,677
  • 2
  • 19
  • 29
7
  1. Install @types/node by running npm i @types/node
  2. Add "types": [ "node" ] to your tsconfig.json file in the compilerSection section.
Muhammad Charaf
  • 343
  • 3
  • 16
3

create a file like global.d.ts


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

tutorial by Christian Höller

feyzullahyildiz
  • 456
  • 4
  • 11
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..."

Paulo Vinícius
  • 326
  • 2
  • 9
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
zenoh
  • 2,071
  • 1
  • 22
  • 38
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

gkri
  • 1,627
  • 3
  • 18
  • 27
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

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
ttfreeman
  • 5,076
  • 4
  • 26
  • 33
1

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.
    }
}
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
}
Ross Coundon
  • 647
  • 1
  • 9
  • 20
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)
Evan Shortiss
  • 1,638
  • 1
  • 10
  • 15
  • 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). – Joe Oct 12 '22 at 02:24
  • Can you try testing this workaround? If successful it can be merged into master https://github.com/evanshortiss/env-var/issues/155 – Evan Shortiss Oct 14 '22 at 03:11
  • I ended up using https://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. – Joe Oct 18 '22 at 16:32
  • 1
    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. – Evan Shortiss Oct 19 '22 at 17:26
0

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' });
atomboyd
  • 29
  • 6