Nodejs, express routes as es6 classes - javascript

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);

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);

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.

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!

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);
}

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).

The above answers seem a bit over complicated. Checkout what I've done here:
class Routes {
constructor(req, res, next) {
this.req = req;
this.res = res;
this.next = next;
this.foo = "BAR"
// Add more data to this. here if you like
}
findAll (){
const {data, res,} = this; // Or just reference the objects directly with 'this'
// Call functions, do whaterver here...
// Once you have the right data you can use the res obejct to pass it back down
res.json ({foo: this.foo}); // Grabs the foo value from the constructor
}
}
Now when it comes to using this class you can do something along the lines of this:
var express = require('express');
var router = express.Router();
var {Routes} = require('./Routes');
router.get('/foo', (req, res, next) => {
new Routes(req, res, next).findAll();
});
I would seperate the two files, so that you just require the Routes class into your Router file.
Hope this helped!

Related

Possible to use router.param() inside a nested router in express.js?

I'm working on a code challenge and trying to challenge myself to make something that works as well as something that is well organized.
I have a router set up and functioning. This router in turn uses an additional router. All is working so far. Now I am trying to run router.param() to preform some logic in the child router using the parameter that comes from the parent router. I have included {mergeParams: true} when creating the child router.
I would like to run additional logic using the parameter for all http methods in the child router, but don't seem to be able to. Can I not use router.param() for this purpose? I also seem to not have access to req.minion in the child router (which was attached using router.param() in the parent).
Alternatively is there a way to carry over logic from the router.param() in the parent router to the child router?
Parent router
const {getFromDatabaseById, getAllFromDatabase} = require('./db');
const express = require('express');
const workRouter = require('./workRouter.js');
const minionsRouter = express.Router();
minionsRouter.use('/:minionID/work', workRouter);
minionsRouter.param('minionId', (req, res, next, minionId) => {
const minion = getFromDatabaseById('minions', minionId);
if(minion){
req.minion = minion;
next();
}else{
res.status(404).send();
}
});
minionsRouter.get('/', (req, res, next) => {
res.send(getAllFromDatabase('minions'));
});
minionsRouter.get('/:minionId', (req, res, next) => {
res.send(req.minion);
});
//Some additional http methods here
module.exports = minionsRouter;
Child router
const {getAllFromDatabase} = require('./db');
const express = require('express');
const workRouter = express.Router({mergeParams: true});
workRouter.param('minionId', (req, res, next, minionId) => {
const allWork = getAllFromDatabase('work');
const minionWork = allWork.filter(work => work.minionId === minionId);
console.log(minionWork);
if(minionWork){
req.minionWork = minionWork;
next();
}else{
res.status(404).send();
}
});
workRouter.get('/', (req, res, next) => {
res.send(req.minionWork);
});
//Some additional http methods here
module.exports = workRouter;
The get methods in the parent router work perfectly, and the get method in the child router works if I move the logic in the router.param() call to it, but as is it does not seem to attach minionWork to req. If router.param() won't work for this, how can I avoid repeating that code for all http methods in the child router? Thanks!

importing modules node.js function overrides another function even without importing

in the following code i'm using expresjs
//index.js
app.use('/',routes());
//app/routes.js
module.exports = function() {
express = require('express');
const loggedUserProfileController = require('../controllers/LoggedUserProfile');
const userProfileController = require('../controllers/userProfile');
const router = express.Router();
router.post('/get-logged-user-profile', loggedUserProfileController.getLoggedUserProfile());
router.post('/get-user-profile-data', userProfileController .getUserProfile());
return router;
}
controllers
//controllers/loggedUserProfile.js
module.exports =
{
getLoggedUserProfile:function(){
return getLoggedUserProfile:function= (req, res, next) => {
getUserCustomData();
}
}
getUserCustomData(){console.log('logged user')}
//controllers/userProfile.js
module.exports =
{
getUserProfile:function(){
return getUserProfile:function= (req, res, next) => {
getUserCustomData();
}
}
getUserCustomData(){console.log('user')}
the output is 'user'
the second getUserCustomData overrides the first one how is that possible regarding that i didn't import it in module.exports
Yes this is possible. A function definition is hoisted, declared, and assigned. If you define another in the same scope after the previous one, the new definition will now overwrite the original.
Identifiers used in function statements/definitions are not constants and are therefore subject to re-assignment. If you want it to remain static after creation you can do const getUserCustomData = function () { ... } instead and the second attempt to re-assign will throw an error for you.

chain middleware functions in custom function

I know that I can chain middleware functions after passing in the route like
const express = require('express');
const router = express.Router();
router.post('/', middlewareFunction1, middlewareFunction2, controllerFunction);
module.exports = router;
I would like to know if it's possible to call only one function (called gateway)
router.post('/', gatewayFunction1);
and this function is able to chain all those methods
const controller = require('../controllers/controller');
function gatewayFunction1(request, response, next) {
// validate route
// do other middleware stuff
// call controller.function1
}
module.exports = {
gatewayFunction1,
};
Why would I do that? I was thinking about separating the middleware logic from the routes. This gateway should just get executed after routing and before calling the router.
I tried to return an array of functions (example code)
function gatewayFunction1(request, response, next) {
return [(request, response, next) => {
console.log('called middleware 1');
next();
}, (request, response, next) => {
console.log('called middleware 2');
next();
}, (request, response, next) => {
response.status(200).json({
message: 'that worked',
});
}];
}
but when I call this api route I get no response
Could not get any response
so it keeps loading forever. Is there a way to chain these middleware functions within another function?
Your gatewayFunction1 does nothing except returns an array.
Just use router.
const express = require('express');
const gatewayFunction1 = express.Router();
gatewayFunction1.use(middlewareFunction1, middlewareFunction2, controllerFunction);
module.exports = gatewayFunction1;
Then
const gatewayFunction1 = require('...'); // where you define gatewayFunction1
router.post('/', gatewayFunction1);
Middleware should be a function and you are returning an array.If next function is not called it will get stuck. I don't like the whole idea combining them but I think the best way is to import all your middleware functions in one function and call them individually then use that function as your combined middleware.

Using classes in routing express

I decided to rewrite the code from functions to classes. However, I encountered such a problem that my this undefined
Routing
// router.js
const ExampleController = require('./ExampleController');
const instanceOfExampleController = new ExampleController();
// Require express and other dependencies
app.post('/post-to-login', instanceOfExampleController.login) // An error appears inside the method
And controller
// My Controller
class ExampleController {
// Private method
myPrivateMethod(info) {
return info.toUpperCase();
}
login(req, res, next) {
console.log('----------------------');
console.log(this); // Here "this" equal of undefined!
console.log('----------------------');
const someValue = this.myPrivateMethod(req.body.info); // Not work!
res.send(someValue);
};
}
instanceOfExampleController.login.bind(instanceOfExampleController) will do the trick. The function loses its context once it's being called directly.
Alternatively, you can use:
app.post('/post-to-login', function (req, res, next) {
instanceOfExampleController.login(req, res, next);
});

Extending express.Router

Is there a way to extend express.Router ?
I tried this :
class Test extends express.Router() {
};
But express throws me an error.
Any solution ?
The right way to do it:
class Test extends express.Router {
constructor() {
super();
this.get('/', (req, res) => console.log('test'));
}
};
When you write express.Router() (with parentheses) you already call the constructor and therefore you're trying to extend an object instead of class.
You can't do that because express.Router is a function. I haven't found how to extend a class with the function. But you can use an approach how this http://brianflove.com/2016/03/29/typescript-express-node-js/ or use standard approach with ES2015:
import * as express from 'express';
let router = express.Router();
/* GET home page. */
router.get('/', (req, res, next) => {
res.render('index', { title: 'Express' });
});
export = router;
Maybe something like this:
function My_Router() {
return express.Router.apply(this, arguments);
}
var myRouter = new My_Router();
Excuse me, I don't know English at all. Thanks to Google Translate ...
The constructor function should return a perfect (function, in fact) object of a router.
You can add to this object, whatever you want, or change it as you wish.
Each interface will have a new router object, of course.
It is also necessary to determine the prototype of the constructor function. This happens in line 6.
const Router = require("express").Router;
const myRouter = function () {
const router = Router();
Object.setPrototypeOf(Router, myRouter);
router.myGet = function (path) {
router.get.call(this, arguments);
console.log("is my get function!!");
};
return router;
};
const router_custom = new myRouter();
router_custom.myGet("/", (req, res) => {
res.send("hay!!");
});
May be something like this would help
class YourController extends express.Router{
constructor(){
super()
this.get("/", (req, res)=>{
//do somthing
res.send("return something");
})
this.get("/:id", (req, res)=>{
//do something
res.send("return something);
})
}
}
module.exports = new YourController();
Use It As :
const yourExtendedRouter = require("../controllers/YourController")
router.use("/base-url", yourExtendedRouter)

Categories