Home > Software design >  I can't understand how do 'global`s work in TypeScript/NodeJS and what is their difference
I can't understand how do 'global`s work in TypeScript/NodeJS and what is their difference

Time:09-20

I am reading a code like below:

import { MongoMemoryServer } from "mongodb-memory-server";
import mongoose from "mongoose";
import request from "supertest";
import { app } from "../app";

declare global {
  function signin(): Promise<string[]>;
}

let mongo: any;

beforeAll(async () => {
  process.env.JWT_KEY = "asdfasdf";
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";

  const mongo = await MongoMemoryServer.create();
  const mongoUri = mongo.getUri();

  await mongoose.connect(mongoUri, {});
});

beforeEach(async () => {
  const collections = await mongoose.connection.db.collections();

  for (let collection of collections) {
    await collection.deleteMany({});
  }
});

afterAll(async () => {
  if (mongo) {
    await mongo.stop();
  }
  await mongoose.connection.close();
});

global.signin = async () => {
  const email = "[email protected]";
  const password = "password";

  const response = await request(app)
    .post("/api/users/signup")
    .send({
      email,
      password,
    })
    .expect(201);

  const cookie = response.get("Set-Cookie");

  return cookie;
};

I can't understand the purpose of global.signin function and how does it work? I guess it has something to do with Jest but as long as I know the Jest codes should be inside the __test__ folder with the same file name and .test.ts extension. But the above function is defined and used inside the setup.ts file in the root of the application.

I also see some codes like following:

declare global {
  namespace Express {
    interface Request {
      currentUser?: UserPayload;
    }
  }
}

In some .ts files of the project as well that I am not sure are these global variables the same as the other globals I mentioned above or these are different things? I am interested to know how this global variables work as well?

CodePudding user response:

The piece of code you shared is making use of global augmentation https://www.typescriptlang.org/docs/handbook/declaration-merging.html#global-augmentation

// Hint typescript that your global object will have a custom signin function
declare global {
  function signin(): Promise<string[]>;
}

// Assign value to global.signin
global.signin = async () => { /* implementation */ };

Likely one or multiple modules ("mongoose", "supertest", "../app") imported by the test file is using global.signin (or window.signin) at some point (or maybe one of their nested imports is => look for "signin(" in the project). Thus for testing purposes, global.signin needed to be mocked. However just adding global.signin = something would raise a typescript error, because signin is not a standard global variable. This is where declare global comes into play. It hints typescript that in your particular context, a signin function is expected to exist in global scope.

CodePudding user response:

JavaScript/TypeScript running in node will try to resolve anything it can't find in the current local scope in global (the same way a browser would look in window). Any function or variable you can access globally (e.g. setTimeout()), can also be accessed with global. as prefix. It just makes it explicit.

What happens in your code are two things:

declare global {
  function signin(): Promise<string[]>;
}

Here it tells typescript's type system that the global object also has a function called signin. This part is not required but it makes sense required for typescript to allow you to access / define that function, in JavaScript you simply define it.
https://www.typescriptlang.org/docs/handbook/declaration-merging.html has some details how declare works.

global.signin = async () => {
   // code ...
};

And here it is actually added to the global object.

In JavaScript non strict mode you could even write (notice the lack of var/let/const/global.)

signin = async () => {
   // code ...
};

I don't see signin getting used anywhere in that code so the reason for it is unclear to me. As long as the file that defines it gets loaded you can call the function simply by referring to it as signin(). The global. is added implicitly.

The purpose of

declare global {
  namespace Express {
    interface Request {
      currentUser?: UserPayload;
    }
  }
}

is more practical, in express you may want to add properties to your requests that get added by middleware. By declaring that the Express Request has a property called currentUser you get to do

app.get((req, res) => {
   const user: UserPayload = req.currentUser
   ...
})

without typescript complaining about an unknown property. More on that for example https://blog.logrocket.com/extend-express-request-object-typescript/

  • Related