Search code examples
javascriptnode.jsexportrequire

require and module.exports : TypeError: X is not a function


File structure :

server
├── controllers
│   ├── locationsController.js
│   ├── mainController.js
│   └── utilisateursController.js
├── models
│   ├── Locations.js
│   ├── Utilisateurs.js
|   └── ...
├── routes.js
└── server.js

In mainController I have a function isValid to check a string, I put it there because I want to access it from both utilisateursController.js and locationsController.js. I export it as follow :

mainController.js

const lc = require('./locationsController');
const uc = require('./utilisateursController');

// ...

module.exports = {
  // Associate all models
  associateAll: function () {
    lc.associateLocations();
    uc.associateUtilisateurs();
  },
  isValid: function(...fields) {
    for (let i = 0; i < fields.length; i++)
      if (fields[i] === undefined || fields[i] === null || fields[i] === '')
        return false;
    return true;
  }
};

Problem

I can access the function isValid from utilisateursController.js, but when I try to do the same from locationsController.js, I have this error:

(node:6461) UnhandledPromiseRejectionWarning: TypeError: mc.isValid is not a function
    at exports.getAllTasks (.../server/controllers/locationsController.js:30:11)

Code

utilisateursController.js

From this file, I can perfectly access isValid, there is no error.

const mc = require('./mainController');

// ...

exports.login = async function (req, res) {

  let response = {
    // ...
  }
  if (req.query == null) {
    response.infoMsg = 'Query empty...';
    res.send(response);
    return;
  }

  const usernameInput = req.query.username;
  const passwordInput = req.query.password;

  if (!mc.isValid(usernameInput, passwordInput)) {
    response.infoMsg = 'username or password is empty...'
    res.send(response);
    return;
  }

  // ...

}

locationsController.js

From this file, I get the error mentioned above, and I really don't know why...

const mc = require('./mainController');

// ...

exports.getAllTasks = async function (req, res) {
  let response = {
    // ...
  }

  const usernameInput = req.params.username;

  if (!mc.isValid(usernameInput)) {
    response.infoMsg = 'No parameters given...';
    res.send(response);
    return;
  }

  // ...

}

What I think

I think it is maybe because of the order of the resolutions of the requires...

What the debugger says

utilisateursController.js

utilisateurs

locationsController.js

locations

I really don't know what is causing this porblem...


Solution

  • The problem is caused by the fact you have a circular relationship between mainController, utilisateursController, and locationsController. Both utilisateursController and locationsController require mainController, and mainContoller requires both utilisateursController and locationsController. As a result, Node.js's CommonsJS-style module resolution ends up running the top-level code in at least one (probably both) of your modules with a placeholder object for the exports of one of the other modules. (Apparently, in your case, locationsController gets a placeholder for mainController's exports. utilisateursController probably does, too, but doesn't try to use it at the top level.)

    If you avoid using mc at the top level, only using it in functions called later, that placeholder will get filled in before you need it and all will be well. The code you've quoted seems to only use mc within a function, but given the error you're getting, apparently that's not true of your real code.

    More in the Node.js modules documentation's "Cycles" section.


    Side note: This doesn't happen with native JavaScript modules (often called "ESM" for "ECMAScript Modules), because even when there are circular dependencies, they're resolved before the top-level module code gets run.