Understand the behavior of require in NodeJS

Viewed 347

I had two javascript files in Node Project. One was router which had routes and corresponding functions in it. Another was a utility class where commonly used functions are written as prototype. say utilservice.getSomething();

The first file calls utilservice.getSomething() in second file.

I imported the first file in second file (which is unnecessary and unused) .Then i called an API in first file.

I got error 500 stating utilservice.getSomething() is not a function.

I spent so much hours thinking something went wrong with use of Promise and tried with async and await and landed in same error.

Very last, i removed the import and found API call happening well.

I was with an impression that require is just for import of methods in another script. But there's something beyond it. There are resources online which would tell purpose of request. But i like to understand this behavior.

Sample:

File1.js

const utilService = require('../utils/utilService');

router.get('/something',function(req,res){
  utilService.getSomething().then((data)=>{
       //do something
 })
});

File2.js

const file = require('../file1');

function util(){}
util.prototype.getSomething = function(){
  return "hello"
}
module.exports = new util();

I hit that /something API. I got utilservice.getSomething is not a function.

3 Answers

While require is usually used to import other modules and scripts, it also executes any code in these modules. This allows modules to overwrite properties of other modules and prototypes of objects. Modules don't require module.exports to be valid to be imported.

Ex:

File1.js

module.exports = {
  foo: ()=>{
    console.log("hello");
  }
};

File2.js

const mod = require("./File1.js");
delete mod.foo;

index.js

const mod = require("./File1.js");
mod.foo(); // hello
require("./File2.js"); // undefined
mod.foo(); // mod.foo is not a function

Congrats, you have landed in the world of circular dependencies. Instead of just telling you what that is, I'll give you an example.

Say you had two files, file1.js and file2.js.
file1.js:

module.exports = {
  doSomething: function() {
    console.log("did something");
  }
};

file2.js:

const util = require("./file1");
util.doSomething();

file1.js exported a function called doSomething, and file2.js called it. So currently, the dependency tree looks like this:

file2.js -> requires -> file1.js

Now problems would start happening when you modify file1.js to this:

const file2 = require("./file2");
module.exports = {
  doSomething: function() {
    console.log("did something");
  }
};

Why? Well, let's look at the dependency tree now.

file2.js -> requires -> file1.js -> requires -> file2.js -> requires -> file1.js

See the problem? file2.js requires file1.js, which requires file2.js, but file2.js requires file1.js (etc, etc). Instead of getting stuck in an infinite loop, Node.JS loads the current state of module.exports from file1.js (which is undefined since the script got stuck on require("./file2")), and that's how you got the TypeError.

In nodeJS, require let you import modules from other files. It helps you split your application and only load necessary files to execute them, instead of having bunches of non used code loaded for nothing.

It actualy exists in many other languages under different names: require(php), include (php), import (typescript, python...etc)

One other thing: to be able to use require properly, you must "export" modules, definitions, functions, classes...etc to be able to use them in another script, using "module.exports" or "exports". So basicaly it has to do with "context", or if you prefer "scope".

This is why there's a folder named "node_modules" when you do a "npm install" using a package.json.

Thanks to "require", you can import only what is necessary, and not the whole project files every time you do something on your app.

Related