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)
Do we have to add this typings file somewhere in
tsconfig
?I have created a file
typings.d.ts
like this:Still TypeScript doesn't detect and VSCode doesn't provide any intellisense.
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.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...
Thank you for this.
Please note that this is a solution for the application running on Node. Not in browser.