using process.env in TypeScript - node.js

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.

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.

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.

just add before use process.env.NODE_ENV follow lines:
declare var process : {
env: {
NODE_ENV: string
}
}

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"

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.

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.

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!;

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');

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.

Install #types/node by running npm i #types/node
Add "types": [ "node" ] to your tsconfig.json file in the compilerSection section.

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

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));

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..."

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

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.
}
}

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

create a file like global.d.ts
declare global {
namespace NodeJS {
interface ProcessEnv {
SECRET: string;
}
}
}
export {};
tutorial by Christian Höller

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
}

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)

Related

Node Js and Typescript call global function with parameters

I have a logger function in logger.ts
export const Logger=(message:string, module?:string, method?:string)=>{
console.log(`${module} ${message}`)
}
In types/global.d.ts
declare global {
var Log: any;
}
export {}
In index.ts
import { Logger } from './src/utils/logger';
global.Log=Logger;
global.Log("Hello world");
It works without any problem. But in global.Log i can't see the parameters and intellisense not working.
How can I see parameters for global.Log?
I believe Intellisense is not working because the variable Log is of any type. Try defining global as below:
declare global {
var Log: (message: string, module?: string, method?: string) => void;
}
export {};
Intellisense will need a specific type (not any or unknown) in order to suggest parameters.
Also, there is no need to prefix Log with global (i.e. global.Log) as Log is in the global context (accessible from anywhere), and it is not possible to use global.Log - global is not a variable.

__env :-Element implicitly has an 'any' type because index expression is not of type 'number'

I was trying to deploy angular node app on aws beanstalk by this blog https://blog.devgenius.io/deploy-angular-nodejs-application-to-aws-elastic-beanstalk-9ab13076a736
So initially I created the folder environments under the assets folder. Added env.js file to the environments folder and added the following code in there:
(function (window) {
window.__env = window.__env || {};
window.__env.SERVER_URL = 'http://localhost:3000';
}(this));
Then I added
<script src=”/assets/environments/env.js”></script>
script to the index.html file.
I then added the following to environment.ts file and environment.prod.ts files:
server_URL: window['__env']['SERVER_URL']
I then got the following error with __env:
Element implicitly has an 'any' type because index expression is not of type 'number'.
TypeScript can use an implicit type for the window object. The default angular configuration will respect that type when building.
You can extend the window type by adding the following on top of the class to use your __env variable (I recommend you do this option):
declare global {
interface YourWindow = Window & { '__env': any; }
}
...
server_URL: (window as YourWindow)['__env']['SERVER_URL']
You could also modify your tsconfig.json file to be more permissive:
{
...
"suppressImplicitAnyIndexErrors": true
}
Using suppressImplicitAnyIndexErrors is quite a drastic approach. It is recommended to use a #ts-ignore comment instead:
export const environment = {
production: false,
// #ts-ignore
server_URL: window["env"]["SERVER_URL"]
};

Conditionally import conditionally existing file in Vue

I need to import a file into my project when an environment variable is set, say dist/built.esm.js. It's implied that when the environment variable is set, the file will exist, otherwise it may or may not exist. It seems straightforward to just wrap a call to import in an if statement that checks for the environment variable, but Vue throws the below warning even if the if statement never passes:
And the code:
if (process.env.VUE_APP_USE_COMPILED == 'true') {
const compiledPackage = require('./dist/built.esm.js')
Vue.use(compiledPackage)
}
Setting the if statement to always be false in a nondeterminate way (setting a string var and then comparing it to a different value, instead of just if (false)) results in the same problem, which rules out any possibility of the environment variable being 'true' when it isn't supposed to be.
A temporary workaround I found is to wrap the import in a try/catch, which instead displays a warning instead of an error:
How can I get rid of the errors and warnings completely? I do want it to still error if the file doesn't exist but the environment variable has been set to true, but it shouldn't fail or warn on compilation if the statement hasn't even executed yet.
Does this work?
if (process.env.VUE_APP_USE_COMPILED == 'true') {
import('dist/built.esm.js')
.then(obj => Vue.use(obj))
.catch(err => console.log(err));
}
I managed to figure this out on my own. I used the resolve.alias property in the Webpack configuration to allow a 'soft fail' when the file doesn't exist. I changed my import to use an alias (my-compiled-package-alias), which would conditionally resolve to either the built file or an empty dummy file (dev/import-dummy.js). I had to use resolve.alias rather than resolve.fallback, since Vue2 uses Webpack v4 which doesn't include the latter property.
My updated code:
if (process.env.VUE_APP_USE_COMPILED == 'true') {
const compiledPackage = require('my-compiled-package-alias')
Vue.use(compiledPackage)
}
In my vue.config.js:
module.exports = {
...
configureWebpack: {
resolve: {
alias: {
"my-compiled-package-alias":
process.env.VUE_APP_USE_COMPILED ? "./dist/built.esm.js": "./import-dummy.js"
}
}
},
...
}
but it shouldn't fail or warn on compilation if the statement hasn't even executed yet
Compilation happens before execution. If you get a compile error, that means something went wrong before your code was executed, including any conditionals.
What I believe happens here is that you're using webpack, and it's trying to include dist/built.esm.js in your bundle. Behind the scenes, webpack actually replaces require with some magic. To get around this, use __non_webpack_require__ instead
You could try setting up a compile-time constant using DefinePlugin in your webpack config, maybe something like
plugins: [
new webpack.DefinePlugin({
// this is resolved compile-time
USE_COMPILED: process.env.VUE_APP_USE_COMPILED == 'true'
})
]
Then, in your code
if (USE_COMPILED) require('./dist/built.esm.js')
Here the value of USE_COMPILED should be replaced by webpack compile-time with true if your environment var is set to 'true', and false otherwise.

How can I use packages that extend `koa.Request` in TypeScript?

I am trying to use koa-tree-router and koa-bodyparser at the same time, but I keep getting TypeScript errors:
export const userLoggingRouter = new KoaTreeRouter<any, DefaultContext>();
userLoggingRouter.post('/logs/action', (ctx) => {
const logEntries = ctx.request.body;
const user = ctx.state.user;
// ...
});
error TS2339: Property 'body' does not exist on type 'Request'.
I have #types/koa-bodyparser installed, and it contains the following definition:
import * as Koa from 'koa';
declare module 'koa' {
interface Request {
body: string | Record<string, unknown>;
rawBody: string;
}
}
But it doesn't seem to do anything. I found this question, but importing koa-bodyparser directly also does not do anything. How do I get TypeScript to recognize the extended Request type?
Edit: Creating a .d.ts file inside my project containing the following:
import {Request} from "koa";
declare module "koa" {
interface Request {
body: any;
}
}
Made the compile error go away, but this seems like an inelegant solution because I would have to copy over type information for every package that modifies koa.Request.
This was happening because I was using Yarn PnP and I had two different versions of #types/koa installed. Once I added a resolutions field to my package.json that forced all of the other TypeScript definitions to use the same version of #types/koa, everything worked.

How to use TypeScript in a Custom Test Environment file in Jest?

I need to enable some global variables to be reachable for my test so I am setting up a Custom Environment to be used in the testEnvironment option in my jest.config.json to achieve that.
For our project we have a TypeScript file that we use for setupFilesAfterEnv option and that works just fine, however the testEnvironment seems to support only ES5. Is there any way to use TypeScript in such option?
I successfully created a Custom Jest Environment using ES5 syntax, however since we are injecting global variables I need TypeScript to also declare a global namespace see: https://stackoverflow.com/a/42304473/4655076.
{
///...
setupFilesAfterEnv: ['<rootDir>/test/setup.ts'], // This works with ts
testEnvironment: '<rootDir>/test/customJestEnvironment.ts', // This doesn't work with ts
}
You might find this helpful: Configure Jest global tests setup with .ts file (TypeScript)
But basically you can only pass in compiled JS files as environments.
You can do what that article suggests. But it didn't work for me out of the box. So I manually compile my env.
i.e.
in package.json
"test": "tsc --lib es6 --target es6 --skipLibCheck -m commonjs --esModuleInterop true path/to/env.ts &&
jest --config=jest.config.js",
And in jest.config.js
{
testEnvironment: '<rootDir>/path/to/env.js', // Note JS extension.
}
I solved this by using ts-node and the following command:
node -r ts-node/register ./node_modules/jest/bin/jest.js
This essentially compiles the typescript on-the-fly, so that jest receives the emitted javascript, without the need of actually compiling your typescript sources to js.
You will need to enable esModuleInterop TS compiler option for this to work properly.
TestEnvironment.ts
import NodeEnvironment from 'jest-environment-node';
import type {Config} from '#jest/types';
class TestEnvironment extends NodeEnvironment {
constructor(config: Config.ProjectConfig) {
super(config);
// this.testPath = context.testPath;
// this.docblockPragmas = context.docblockPragmas;
}
public async setup(): Promise<void> {
await super.setup();
console.log('SETTING UP...');
// await someSetupTasks(this.testPath);
// this.global.someGlobalObject = createGlobalObject();
// // Will trigger if docblock contains #my-custom-pragma my-pragma-value
// if (this.docblockPragmas['my-custom-pragma'] === 'my-pragma-value') {
// // ...
// }
}
public async teardown(): Promise<void> {
await super.teardown();
console.log('TEARING DOWN!');
// this.global.someGlobalObject = destroyGlobalObject();
// await someTeardownTasks();
}
}
export default TestEnvironment;
This solution however, will break globalSetup -- if you use jest-ts.
As you might know, typescript files are just superset to javascript to provide strong type checking. Jest's engine/runtime however expects your files in CommonJS format javascript files.
You can have a separate tsconfig.env.json just for this env.ts. Compile this before running jest test and use the compiled env.js in your jest.config.js.
tsc -p tsconfig.env.json && jest
Also i have never seen people writing configuration files in TS.
why CommonJS ? because jest is essentially running on top of node. node supports Javascript files in CommonJS format. Node has started supporting es modules as well recently! This is a big thing!
You can create a global.d.ts file at the root of your project.
Then you can define global variables as seen below. In my case, it was a NestJS application, but you can define anything.
declare global {
namespace NodeJS {
interface Global {
app: INestApplication;
}
}
}
This is another example for client project where we define window properties like innerWidth;
declare namespace NodeJS {
interface Global {
innerWidth: number;
dispatchEvent: Function;
}
}
Inside your .d.ts definition file:
type MyGlobalFunctionType = (name: string) => void
Add members to the browser's window context:
interface Window {
myGlobalFunction: MyGlobalFunctionType
}
Same for NodeJS:
declare module NodeJS {
interface Global {
myGlobalFunction: MyGlobalFunctionType
}
}
Now you declare the root variable
declare const myGlobalFunction: MyGlobalFunctionType;
Then in a regular .ts file, but imported as side-effect, you actually implement it:
global/* or window */.myGlobalFunction = function (name: string) {
console.log("Hey !", name);
};
And finally use it elsewhere :
global/* or window */.myGlobalFunction("Ayush");
myGlobalFunction("Ayush");

Resources