DEV Community

Edouard Penin
Edouard Penin

Posted on

Typing process.env and dealing with NODE_ENV

I don't know about you but I hate using process.env, it's like drunk typescript: "process.env.blah.indeed().next().var ? Yeah dude, let's party πŸ˜›".

Well drunk typescript kinda sucks. Let's fix that πŸ‘¨β€πŸ³

TLDR;

Install node typings

$ npm install --save @types/node

Extend node typings (.d.ts file version)

// someDefinitionFile.d.ts

// Target the module containing the `ProcessEnv` interface
// https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
declare namespace NodeJS
{

    // Merge the existing `ProcessEnv` definition with ours
    // https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces
    export interface ProcessEnv
    {
        NODE_ENV: "development" | "production" | "test";
        MY_API_KEY: string
        DB_USER?: string
        // ...
    }
}

Extend node typings (.ts file version)

// someFile.ts

// Required when adding declarations inside a module (.ts, not .d.ts)
// If you have documentation about why this is required I would love to know πŸ€“
declare global
{
    // Target the module containing the `ProcessEnv` interface
    // https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
    namespace NodeJS
    {
        // Merge the existing `ProcessEnv` definition with ours
        // https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces
        export interface ProcessEnv
        {
            NODE_ENV: "development" | "production" | "test";
            MY_API_KEY: string
            DB_USER?: string
            // ...
        }
    }
}

Done

Yup, that's it πŸ€—

Bonus tip, deal with if( process.env.NODE_ENV === "something" )

So now we've can define what values process.env.NODE_ENV can have (no more "is it dev or development..?" 😌).

Usually we use that to do stuff like if (process.env.NODE_ENV === "development") with type safety.

That totally works, but what we really want to express is usually: do that in dev only or unless in prod, do that.

Here's a simple way to make that more obvious in your codebase:

const isProd = () => process.env.NODE_ENV === "production" || process.env.NODE_ENV === "staging" // Production here is a concept, not a value, for you it might include staging 

const isDev = () => !isProd()

export const devOnly = (action: () => void) => 
        isDev()  ?          
        action() :
        null


export const prodOnly = (action: () => void) => 
        isProd() ?          
        action() :
        null

export const unlessProd = (action: () => void) => 
        !isProd() ?          
        action()  :
        null


export const unlessDev = (action: () => void) => 
        !isDev() ?          
        action() :
        null

devOnly(()=> console.log("We're in dev mode πŸ₯³"))

Same thing for the 0 duplication guys πŸ˜‰

type validator = () => boolean

type action    = () => void

const doIf = (condition: validator) =>
    (action: action) => 
        condition()  ?          
        action() :
        null

const not = (condition: validator) =>
 () => !condition()

const isProd = () => process.env.NODE_ENV === "production" || process.env.NODE_ENV === "staging" 

const isDev = not(isProd)

export const devOnly    = doIf(isDev)
export const prodOnly   = doIf(isProd)
export const unlessProd = doIf(not(isProd))
export const unlessDev  = doIf(not(isDev))
// ... testOnly, QA only, etc

devOnly(()=> console.log("We're in dev mode πŸ₯³"))

Going further

Extending existing definitions is called declaration merging

What we've done is called Module augmentation

Keep In Touch

You disagree, you have questions, something doesn't feel right, let's chat! Leave a comment or reach me via Twitter or Linkedin

Top comments (5)

Collapse
 
ryands17 profile image
Ryan Dsouza • Edited

Do we have to add this typings file somewhere in tsconfig?

I have created a file typings.d.ts like this:

declare namespace NodeJS {
  export interface ProcessEnv {
   PORT: number;
  }
}
Enter fullscreen mode Exit fullscreen mode

Still TypeScript doesn't detect and VSCode doesn't provide any intellisense.

Collapse
 
furiouskj profile image
Kenny M

For anyone who discovers this in the future, add "types": "./src/main.d.ts" to package.json where the value is the path to your typescript definitions. Visual Studio Code will pick up on this.

Collapse
 
yassineldeeb profile image
Yassin Eldeeb πŸ¦€ • Edited

Hey I've just made a VS Code extension to automate that process.

Check it out πŸ‘‡

Blog:
dev.to/yassineldeeb/typing-process...

Market place:
marketplace.visualstudio.com/items...

Github repo:
github.com/YassinEldeeb/Env-Typing...

Collapse
 
rafaelrozon profile image
Rafael Rozon

Thank you for this.

Collapse
 
karelkral profile image
Karel Kral

Please note that this is a solution for the application running on Node. Not in browser.