1

I am a junior NodeJS dev, currently working on a cryptocurrency exchange platform. here is the project folder structure.

/app
    /controllers
        /user
        /order
        /settings
        ...
        index.js
    /middlewares
    /models
    /routes
        user.js
        order.js
    /services
        user.js
        order.js
        ...
    /views
    index.js
/config
/migrations
/public
/utils
server.js
.env
...

Now, at first, it was a bit overwhelming, but later on, I became comfortable moving around the app.
except for one particular file! the index.js in the controllers directory, here is how it is set up.

const { readdirSync, lstatSync } = require('fs');
const { validationSchema } = require('../../utils/validator');

module.exports = readdirSync('app/controllers')
  .filter(name => lstatSync(`app/controllers/${name}`).isDirectory())
  .reduce((controllersAccumulator, dir) => Object.assign(
    controllersAccumulator,
    {
      [`${dir}Controller`]: readdirSync(`app/controllers/${dir}`)
        .map(fileName => require(`./${dir}/${fileName}`))
        .reduce((accum, controllerFile) => Object.assign(
          accum,
          Object.keys(controllerFile).reduce(validationSchema.bind(null, dir, controllerFile), {}),
        ), {}),
    },
  ), {});

I have to admit, this has been always scary to me, just to look at it! so what it does in simple words is, it maps the routes requests to the handlers in the controllers directory.

so for example, If a user wants to make a post request to register a new account: the route path will be like so:

// in the routes dir, user.js 
const { userCOntroller } = require('../controllers/');

router.post('/registration', userController.registration);


// in the /controllers, then /user, there will be a registration.js that includes:
const UserService = require('../../services/user');

exports.registration = async (req, res) => await UserService.create(req.body);


//then in the /services directory, user.js
...
class UserService {
  static create(body) { ... }
  ...
}

module.exports = UserService

so what I am still unable to understand is, how did we come about to have the userController which is imported in the user routes in the last snippet? so this is what the index.js file in the /controllers has produced!
when I asked the senior guys in the team, they said, yes it's hard to read but its less code. well, ok :\

so, what could have been done differently to make this file more readable, in other words, is there a way to refactor it? thanks in advance!

6
  • The first code snippet is indeed pretty hard to read because there aren't ANY comments Commented May 8, 2019 at 13:25
  • 1
    To partially answer your question, userController comes from [${dir}Controller] because ${dir} is replaced with the directory name like 'user', 'order', 'settings' etc, so the resulting module properties are called userController, orderController and so on. The last code snippet uses destructuring to extract one of these properties, in this case, the userController. That's where the name comes from. The actual controller is imported by the following statement: require(./${dir}/${fileName}). The required files are then filtered (reduced) by the validationSchema function. Commented May 8, 2019 at 13:54
  • yes I saw what you mean, thanks very much, but what If I want to import the actual controller and directly use it in the user routes file, for example, instead if importing userControllers and then call userController.registeration I want to import registration function directly in the user routes file, and where should I pass the req.body in this case? thanks again,, Commented May 8, 2019 at 14:03
  • plus, the userController.registration is just a file as far as I understand, I mean its not a function call, how is that ?? I mean how is this req.body passed in the particular situation ?? Commented May 8, 2019 at 14:06
  • I'm assuming that the file registration.js exports an object which contains the registration function and resides inside the user directory. If that's the case, then validationSchema should return its second argument (controllerFile). That's how the registration function can appear inside the userController object. In order to use this function directly, instead of destructuring the whole user controller, you could do something like this: const { userController: {registration} } = require('../controllers/');. Then you could call the registration function directly. Commented May 8, 2019 at 14:49

1 Answer 1

1

The easiest way to explain the first code snippet would be to rewrite it and add appropriate comments:

//Get all file names inside the controllers directory
const fileList = readdirSync('app/controllers');
//Get only directories from the file list
const onlyDirectories = fileList.filter(name => lstatSync(`app/controllers/${name}`).isDirectory());
//Create an empty object that will be exported from this module. Properties of this object will be assigned in the "for of" loop
const objectToExport = {};
//Import all files from the given directory and assign them to objectToExport
for (let directoryName of onlyDirectories){
    //First get all file names
    const controllerFiles = readdirSync(`app/controllers/${directoryName}`);
    //Then require each of these files
    const controllerModules = controllerFiles.map(fileName => require(`./${directoryName}/${fileName}`));
    //Assign the imported modules to `objectToExport`
    controllerModules.forEach((controllerModule) => {
        //Validate each module and assign it only if it passes validation
        if (validationSchema(directoryName, controllerModule)){
            objectToExport[`${directoryName}Controller`] = controllerModule;
        }
    });
}

module.exports = objectToExport;

Below I'll address your follow-up questions from the comments. The resulting object now looks like this:

{
    userController: {
        registration: [Function],
        ...
    },
    orderController: {
        ...
    },
    ...
}

The registration function ended up in the userController property because it was exported by registration.js, which was then imported by the require statement in my first code snippet. In order to use this function directly in other files, you have to destructure it in the following way:

const { userController: {registration} } = require('../controllers/');
//now you can use it like this:
router.post('/registration', registration);

Your last question is about req.body. As you can see, the function takes two parameters: req and res:

exports.registration = async (req, res) => await UserService.create(req.body);

It is then passed to your router as a middleware. I'm assuming you're using Express.js as your framework. If so, req and res are passed automatically by the router to the registration function. That's how they can be used by UserService inside this function. The body property is created automatically by Express as described here.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.