Nodejs, express routes as es6 classes

25,527

Solution 1

import express from 'express';
const app = express();

class Routes {
    constructor(){
        this.foo = 10
    }

    const Root = (req, res, next) => {
        res.json({foo: this.foo}); // TypeError: Cannot read property 'foo' of undefined
    }
}

const routes = new Routes();
app.get('/', routes.Root);
app.listen(8080);

Here is minor rewrite of the code, but as some of the answers here pointed out, the function itself when referenced like that in the route config is unaware of this and has to be binded. To get around this, instead of writing "normal" functions, you just have to write define "fat arrow" functions that automatically bind themselves and you are good to go!

Solution 2

try to use the code to pin this:

app.get('/', routes.Root.bind(routes));

You can get out of the boilerplate using underscore bindAll function. For example:

var _ = require('underscore');

// ..

var routes = new Routes();
_.bindAll(routes, 'Root')
app.get('/', routes.Root);

I also found that es7 allows you to write the code in a more elegant way:

class Routes {
    constructor(){
        this.foo = 10
    }

    Root = (req, res, next) => {
        res.json({foo: this.foo});
    }
}

var routes = new Routes();
app.get('/', routes.Root);

Solution 3

This is happening because you've passed a method as a standalone function to express. Express doesn't know anything about the class that it comes from, therefore it doesn't know which value to use as this when your method is called.

You can force the value of this with bind.

app.get('/', routes.Root.bind(routes));

Or you can use an alternative construct for managing routes. You can still make use of a lot of the syntactic benefits for object oriented programming without classes.

function Routes() {
  const foo = 10;

  return {
    Root(req, res, next) {
      res.json({ foo });
    }
  };
}

const routes = Routes();
app.get('/', routes.Root);
app.listen(8080);
  • You won't have to worry about the value of this
  • It doesn't matter whether the function is called with new or not
  • You can avoid the complexity of calling bind on each route

There's a good list of resources here, on why ES6 classes are not as good as they might seem.

Solution 4

Or if you don't like binding the context per routes, you can optionally bind it to methods in your class' constructor itself.

E.g:

constructor() {
   this.foo = 10;
   this.Root = this.Root.bind(this);
}

Solution 5

We recently refactored all our Express controllers to use a base controller class, and also ran into this issue. Our solution was to have each controller bind its methods to itself by calling the following helper method from the constructor:

  /**
   * Bind methods
   */
  bindMethods() {

    //Get methods
    const proto = Object.getPrototypeOf(this);
    const methods = [
      ...Object.getOwnPropertyNames(Controller.prototype),
      ...Object.getOwnPropertyNames(proto),
    ];

    //Bind methods
    for (const method of methods) {
      if (typeof this[method] === 'function') {
        this[method] = this[method].bind(this);
      }
    }
  }

This ensures that both the parent Controller methods and any custom methods in the child controller class are bound correctly (e.g Foo extends Controller).

Share:
25,527

Related videos on Youtube

Michael Malura
Author by

Michael Malura

Updated on February 26, 2020

Comments

  • Michael Malura
    Michael Malura about 4 years

    I want to clean up my project a bit and now i try to use es6 classes for my routes. My problem is that this is always undefined.

    var express = require('express');
    var app = express();
    
    class Routes {
        constructor(){
            this.foo = 10
        }
    
        Root(req, res, next){
            res.json({foo: this.foo}); // TypeError: Cannot read property 'foo' of undefined
        }
    }
    
    var routes = new Routes();
    app.get('/', routes.Root);
    app.listen(8080);
    
  • Huston Hedinger
    Huston Hedinger almost 8 years
    until es7 gets here - I love _.bindAll I was completely unaware of it. It is much nicer than binding users to itself in every route!
  • Michael Leanos
    Michael Leanos over 7 years
    This seems a lot more complicated than the accepted answer. This requires you to have private members of your class for req, res, and next(). These class members really are just middleware functions that need only to provide the same signature as the desired Express middleware type. Using the .bind method is the best solution to this design consideration. As a note, I'm doing this in TypeScript & seems to be working out great.
  • Craig Myles
    Craig Myles over 5 years
    According to the underscore docs you need to provide a list of the method names to be bound: _.bindAll(object, *methodNames), so _.bindAll(routes, 'Root') in your example.
  • Andre Ravazzi
    Andre Ravazzi about 4 years
    It seems you cannot use const inside a class to define a member function if you're not using TS. I'd also like to note that using arrow functions inside class makes that function "private" to inherit classes, i.e., super.Root() wouldn't work.