`this` is undefined in expressJS route handler - javascript

groups.js
class groupsCtrl {
constructor() {
this.info = "test";
}
get(res, req) {
console.log("LOG ! ", JSON.stringify(this));
}
}
module.exports = new groupsCtrl(); //singleton
routes.js
var express = require('express');
var router = express.Router();
var groupsCtrl = require('controllers/api_admin/groups.js');
router.get('/groups/', groupsCtrl.get);
This logs LOG ! undefined
How can I have access to this in my controller class ?

You need to bind the method to the instance.
One solution:
router.get('/groups/', groupsCtrl.get.bind(groupsCtrl));
Another solution:
constructor() {
this.info = "test";
this.get = this.get.bind(this);
}
Or use something like es6bindall (which basically does the same as the code above, but is perhaps a bit more useful when you need to bind more than one method).

class groupsCtrl {
constructor() {
this.info = 'test';
}
get = (res, req) => {
console.log('LOG ! ', JSON.stringify(this));
};
}
You can just use arrow function to avoid boilerplate code

2020 Update
While both solutions posted by #robertkiep get the job dont, I want to emphasize that both look ugly and are not maintainable
Method 1 which does router.get('/groups/', groupsCtrl.get.bind(groupsCtrl)) looks really ugly when you have a large number of routes
Method 2 gets cumbersome when your controller has many routes
Since your example has only 1 route let me illustrate the problem
Using method 2
class AuthController {
constructor({ db, pgp, logger }) {
super({ db, pgp, logger })
this.postLogin = this.postLogin.bind(this)
this.postLogout = this.postLogout.bind(this)
this.postSignup = this.postSignup.bind(this)
this.postForgot = this.postForgot.bind(this)
this.getReset = this.getReset.bind(this)
this.postReset = this.postReset.bind(this)
}
postLogin(req, res, next) {
}
postLogout(req, res, next) {
}
async postSignup(req, res, next) {
}
async postForgot(req, res, next) {
}
async getReset(req, res, next) {
}
async postReset(req, res, next) {
}
}
Each time you add a new method, the constructor needs to be updated further
Method 3
This in my opinion is a lot cleaner, doesnt need maintenance and you can keep adding methods as you want
The idea is to use the Object.hasOwnPropertyName to get an array of all method names and then bind them programmatically
For example if you write Object.hasOwnPropertyName(AuthController.prototype) it will give you ALL NON STATIC methods in an array
In the example above you will get ['constructor', 'postLogin', 'postLogout'...]
If you call Object.hasOwnPropertyName(AuthController) you get STATIC methods
Lets invoke them programmatically
This controller requires little to no maintenance except keep the static and non static methods in mind, remove the constructor by filtering it out and then invoke bind on each
class AuthController {
constructor({ db, pgp, logger }) {
super({ db, pgp, logger })
this.postLogin = this.postLogin.bind(this)
this.postLogout = this.postLogout.bind(this)
this.postSignup = this.postSignup.bind(this)
this.postForgot = this.postForgot.bind(this)
this.getReset = this.getReset.bind(this)
this.postReset = this.postReset.bind(this)
Object.getOwnPropertyNames(AuthController.prototype)
.filter((propertyName) => propertyName !== 'constructor')
.forEach((method) => (this[method] = this[method].bind(this)))
}
postLogin(req, res, next) {
}
postLogout(req, res, next) {
}
async postSignup(req, res, next) {
}
async postForgot(req, res, next) {
}
async getReset(req, res, next) {
}
async postReset(req, res, next) {
}
}

The answer above is great, I want to add a little bit to help clarify:
Assume we have a class:
class TClass {
constructor(arg) {
this.arg = arg
}
test() {
console.log(this.arg)
}
}
This will NOT work:
const t = new TClass("test")
const method = t.test // method is simply a reference without context
method() // 'this' is not defined
This will work:
const t = new TClass("test")
t.test() // log 'test'
And the reason is like the comments above, the reference to the function doesn't have a context

Related

Mock a middleware function using jest

This is my middleware,
export const parseUser = (req, res, next) => {
const token_header = req.header('Authorization');
if (token_header) {
req.user = jwt_decode(token_header.split(' ')[1].split(' ')[0]);
req.token = token_header.split(' ')[1].split(' ')[0];
next();
} else {
next();
}
};
this is my router,
router.get('/get/', parseUser, swaggerValidation.validate,
async(req, res) => {
...
} catch (err){
...
}
});
i am trying to mock the parseUser function and assign req.user and req.token values using jest and pass it, i need those values to authorize user and need the value assigned to do database query, I am using jest to mock the functions, I have tried google and stackoverflow, i was not able to solve it with those example, i have tried below methods and others,
jest.mock('../../utils/permission');
const mockedParseUser = jest.mocked(parseUser, true)
mockedParseUser.mockReturnValueOnce({req.user: "value", req.token: "value");
i have also tried,
const return = {req.user: "value", req.token: "value"}
const mockedReturn = jest.fn((): any => return}
jest.spyOn('../../utils/permission', parseUser).mockImpementation((): any => mockReturn())
Nothing worked for me, can someone help me with mocking the parseUser().

Run route level middleware conditionally

I've spent quite a bit of time looking to see if there is anything available that will run blocks of route level middleware conditionally.
Ideally the solution would require no changes to the middlewares, and they could just be passed in as an array.
I have just implemented a very crude solution but wanted to see what people think, what issues I'm likely to face and whether there is just a better solution to this.
An example of the problem I am trying to solve is middleware1 altered something on the request that meant middleware2, 3 & 4 do not need to run.
Here's my crude solution:
function conditional({ condition, field, value }, middleware) {
const conditions = {
eq: (_this, _that) => {
return _this === _that;
},
};
return (req, res, originalNext) => {
if (!conditions[condition](_.get(req, field), value)) {
return originalNext();
}
let i = 0;
function next() {
if (i === middleware.length) {
return originalNext();
}
if (typeof middleware[i] === 'function') {
i += 1;
middleware[i - 1](req, res, next);
}
}
next();
};
}
app.get(
'/test-conditional/:condition',
middleware1,
conditional({ condition: 'eq', field: 'params.condition', value: 'run' }, [
middleware2,
middleware3,
middleware4,
]),
middleware5,
async (req, res, next) => {
res.sendFile(path.join(__dirname + '/index.html'));
}
);
Any feedback or pointers in the right direction would be much appreciated.

How to tell typescript req.user in Passport JS never will be undefined?

When using Passport JS, the req.user inside the route, is treated as possible undefined. But the middleware before my route method is making sure that this isn't the case. How to tell that to typescript?
Object is possibly 'undefined'.
Example:
someMethod = async (req: Request, res: Response) => {
const { user } = req
const userId: number = user.id
}
In the above typescript throw an error because user.id is possibly undefined.
I sure could do something like this:
if (!user) return
const userId: number = user.id
But I believe that repeating this piece of code over and over through my methods is not the best approach because middleware is already doing that before even reach the route method.
I suggest you to to simply declare global Express namespace with params that you need like that:
declare global {
namespace Express {
interface Request {
user: User //or other type you would like to use
}
}
}
After that you will be able to user req.user without //#ts-ignoreor optional chaining.
someMethod = async (req: Request, res: Response) => {
const { user } = req
const userId: number = user.id //user will be defined
}

Passing req.params around in Node MVC

I'm trying to figure out how to pass req.params around using Express in the context of MVC.
I know how to properly reference req.params but when I split my app.js up into models and controllers I'm quite lost.
Code for reference:
routes.js
app.get('/category/:category', descriptor.getSingleCategory)
model.js
let getSingleCat = (cb) => {
let queryString = 'SELECT * FROM categories WHERE category_id = $1'
let queryValue = [req.params.category]
db.query(queryString, queryValue, cb)
}
controller.js
const getSingleCategory = (req, response) => {
console.log(req.params.category);
db.desc.getSingleCat((err, queryRes) => {
if (err) {
//render something went wrong
response.send('something went wrong')
} else {
response.send(queryRes.rows)
}
})
}
I've checked all requires and they are working correctly. Is there a vanilla way of passing req.params around without using middleware?
The only way to use the req.params in the model, is by sending it as parameters as the following example:
model.js
let getSingleCat = (params, cb) => {
let queryString = 'SELECT * FROM categories WHERE category_id = $1'
let queryValue = params.category
db.query(queryString, queryValue, cb)
}
controller.js
const getSingleCategory = (req, response) => {
console.log(req.params.category);
db.desc.getSingleCat(req.params, (err, queryRes) => {
if (err) {
//render something went wrong
response.send('something went wrong')
} else {
response.send(queryRes.rows)
}
})
}
You can't use global vars, so this is the only way to do it. Also, this variable (req) can only be accessed in the functions bound to an endpoint that will receive an actual request.

Property in class is undefined when used in class method

Writing a test REST api with NodeJs for learning purposes.
Currently I only have 1 route which accepts a parameter (which works fine).
I'm using an express router to route the GET request to my controller.
All of the routing is working as expected.
My ServiceController currently has a ctor function which accepts 2 parameters. Both of these parameters are passed into the ctor function by the router during instantiation.
In the ServiceController ctor I store the parameters in to fields.
The issue is, when I try to access these fields in a class method I'm getting a "TypeError: Cannot read property 'exec' of undefined".
I did write both of these values to the console to ensure that the ServiceController was receiving these values correctly (which it is).
So, i'm unsure why im getting this error when I attempt to access either "this.exec" or "this.logger" in the get method.
Router
import express from 'express';
import { exec } from 'child-process-promise';
import ServiceController from '../controllers/serviceController';
let routes = (logger) => {
const router = express.Router();
let controller = new ServiceController(exec, logger);
router.route('/status/:name')
.get(controller.get);
return router;
};
module.exports = routes;
ServiceController
export default class ServiceController {
constructor(childProcess, logger) {
this.logger = logger;
this.exec = childProcess;
}
get(req, res) {
if (!req.params.name) {
res.status(400).send('A service name was not provided');
} else {
this.exec(`sc query ${req.params.name}`).then(result => {
if (result.stderr) {
this.logger.log.warn(`stderr: ${result.stderr}`);
}
const regex = /STATE\s+:\s+\d+\s+(\w+)/;
let [, status] = result.stdout.toString().match(regex);
if (!status) {
throw new Error('Status query unsuccessful');
}
let service = {
name: req.params.name,
status: status
};
res.json(service);
return service;
}).catch(error => {
this.logger.log.error(`${error.name} ${error.message}`);
res.status(500).send('An error occurred while executing command');
});
}
}
}
It's a problem of this context. You use the get method on a context which is not your ServiceController instance.
Bind the method on your instance:
router.route('/status/:name')
.get(controller.get.bind(controller));
Or you can also define an arrow function in the ServiceController class.

Categories