About NodeJS globals

NodeJS allows you to set process-level global values that can be accessed in any module.

In general I would consider implicitly sharing global values between modules a bad practice, however in certain situations it can be a pragmatic choice.

Assigning globals using pure JavaScript is straightforward, making them work with TypeScript is another thing. Let’s dive in.

NodeJS globals in JavaScript

Setting a global value in NodeJS couldn’t be simpler:

1
2
3
4
5
6
7
8
global.someValue = 'My hovercraft is full of eels';

// In another module:

console.log(global.someValue);

// Omitting `global` works too:
console.log(someValue);

NodeJS globals in TypeScript

Note: Make sure that you have @types/node installed.

Let’s start by trying to just assign the value and see what happens:

1
2
global.someValue = 'My hovercraft is full of eels';
// TypeError: Property 'someValue' does not exist on type 'Global'.

If we look at the type of global it’s NodeJS.Global. Fortunately TypeScripts declaration merging allows us to extend this interface to include our new attribute:

1
2
3
4
5
6
7
8
9
10
11
12
13
declare global {
namespace NodeJS {
interface Global {
someValue: string;
}
}
}

// Assignement works fine now
global.someValue = 'My hovercraft is full of eels';

// It also can be accessed everywhere now:
console.log(global.someValue); // OK!

This declaration can be placed in any .ts file in your project. Just make sure that it is imported and the global value is assigned before any other module would use it.

One use case remains though:

1
2
console.log(someValue);
// Cannot find name 'someValue'

This error is type level only as the value should be available in the runtime already. To fix it we can just declare it in the global scope as well.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
declare global {
someValue: string; // <-- Same type as below

namespace NodeJS {
interface Global {
someValue: string;
}
}
}

global.someValue = 'My hovercraft is full of eels';

// Both ways to access the value work fine now:

console.log(global.someValue); // OK

console.log(someValue); // OK

Note that the global scope and NodeJS global object are two different things from TypeScripts point of view, hence the duplicated declaration. global is actually a variable declared in the global scope.

1
2
3
// in @types/node

declare var global: NodeJS.Global;

Actual real life use cases

Making winston logger globally available:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// logging.ts

import * as winston from 'winston';

declare global {
type Log = winston.Logger;
const log: Log;
namespace NodeJS {
interface Global {
log: Log;
}
}
}

export const setDefaultLogger = (logger: winston.Logger) =>
(global.log = logger);

// index.ts

import * as winston from 'winston';
import * as logging from './logging';

logging.setDefaultLogger(winston.createLogger(...));

// someModule.ts:

// Note - no need to import 'log'!

const doImportantStuff = () => {
log.info('Doing important stuff!');
}

Making ramda globally accessible:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import * as ramda from 'ramda';

declare global {
const R: typeof ramda;
namespace NodeJS {
interface Global {
R: typeof ramda;
}
}
}

global.R = ramda;

// someModule.ts:

// Note - no imports!

const doImportantStuff = () => {
const x = R.sortBy(...);
}

Final thoughts

You may wonder if it makes sense to use an implicit global instead of just importing the value explicitly.

I don’t think there is one answer to that. For example, in my case, using the implicit log from example above felt smoother than having to import it every time when I wanted to log something, even with IDE automated imports. I feel that this made me use log more often which led to slightly better quality of application logs. It can also remove the headache of constantly adding and removing the import statement if it turns out that the last log was removed and linter throws errors at you that it is no longer needed. Also having the globals explicitly typed, with clear lifecycle doesn’t make them look like a hack.

Just keep in mind that abusing global can lead to a codebase that is both hard to read and reason about.