skip to Main Content

I am working on an Express.js project with a UserController class, with methods like getAllUsers and findUserById. To use these methods in my User router, I find myself binding each method upon creating an instance of UserController to ensure the correct "this" context (refer to this Stack Overflow question).

// UserRouter.js

const express = require('express');
const userController = require('./UserController');

const userRouter = express.Router();

userRouter.get('/users', userController.getAllUsers.bind(userController));
userRouter.get('/users/:id', userController.findUserById.bind(userController));

module.exports = userRouter;

Is there a more efficient and maintainable way to handle the context binding issue in Express.js UserController?

An alternative solution is to bind all methods in the UserController constructor. However, this approach doesn’t feel clean as it necessitates modifying the constructor every time I add a new method.

// UserController.js

class UserController {
  constructor() {
    this.getAllUsers = this.getAllUsers.bind(this);
    this.findUserById = this.findUserById.bind(this);
    // more method bindings as needed
  }

  // Methods implementation
}

module.exports = UserController;

2

Answers


  1. // UserController.js
    class UserController {
      //inject services here if needed and you can access them through "this" inside methods implementations
      constructor() {}
    
      getAllUsers() {
        return async (req, res, next) => {
          try {
            // "this" here refers to the UserController class
            return res.status(200).json({});
          } catch (error) {
            next(error);
          }
        };
      }
    }
    
    module.exports = UserController;
    

    as for the router handler it would like this

    // UserRouter.js
    userRouter.get('/users', userController.getAllUsers());
    

    If you are using typescript you should type it as such

    interface IConfigController {
      get: () => (req: Request, res: Response, next: NextFunction) => Promise<Response | undefined>;
    }
    

    so it is basically a function that accepts whatever needed and returns a handler that returns a promise

    the promise returned should return whatever your function returns so in case of my example i am returning the response or undefined in case of an error that is why it is typed Promise<Response | undefined>

    Login or Signup to reply.
  2. Use arrow functions instead of methods:

    export default class UserController {
      constructor(dependencies) {
        this.dependencies = dependencies;
        this.getAllUsers = (req, res) => {
          // implementation
        };
        this.findUserById = (req, res) => {
          // implementation
        };
      }
    }
    

    You can also declare them as class fields:

    export default class UserController {
      constructor(dependencies) {
        this.dependencies = dependencies;
      }
      getAllUsers = (req, res) => {
        // implementation
      };
      findUserById = (req, res) => {
        // implementation
      };
    }
    

    This is has some problems, but they’re mostly related to inheritance and I doubt you want to subclass your controllers (use composition instead).

    Alternatively, use a function instead of a class:

    export default function UserController(dependencies) {
      return {
        getAllUsers(req, res) {
          // implementation
        },
        findUserById(req, res) {
          // implementation
        },
      };
    }
    

    where you can refer to the dependencies by closure, not as a property of this (which again is not bound).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search