this is undefined in typescript class - node.js

I am trying to understand why "this" can be undefined in classes sometimes, and how to fix it:
import { UserDatabase } from './user.database';
export class UserService {
private userDatabase = new UserDatabase();
async login (request: Request, response: Response) {
let {email, password} = request.body;
const user = await this.userDatabase.login(email, password);
...
Error log:
(node:3254) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'userDatabase' of undefined

The usual problem with the this.
You could bind the method to the class context:
export class UserService {
constructor () {
this.login = this.login.bind(this);
}
...
}
Read more here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind

Related

NestJS | Passport: TypeError: Cannot read properties of undefined (reading 'logIn')

Situation:
Developing api in nest & grapqhql
Worked on one laptop, everything was working well
Then cloned my repo on other laptops, installed dependencies, created a new local database.
App is being built with no errors
When following localhost:4000 in browser to open graphql playground I'm receiving 500 error end next message:
ERROR [ExceptionsHandler] Cannot read properties of undefined (reading 'logIn')
TypeError: Cannot read properties of undefined (reading 'logIn')
at authenticate (/home/gleb/Projects/artwine-api/node_modules/passport/lib/middleware/authenticate.js:96:21)
at /home/gleb/Projects/artwine-api/node_modules/#nestjs/passport/dist/auth.guard.js:91:3
at new Promise (<anonymous>)
at /home/gleb/Projects/artwine-api/node_modules/#nestjs/passport/dist/auth.guard.js:83:83
at JWTAccessAuthGuard.<anonymous> (/home/gleb/Projects/artwine-api/node_modules/#nestjs/passport/dist/auth.guard.js:49:36)
at Generator.next (<anonymous>)
at fulfilled (/home/gleb/Projects/artwine-api/node_modules/#nestjs/passport/dist/auth.guard.js:17:58)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
Code of a passport lib function where the error is caught:
return function authenticate(req, res, next) {
req.login =
req.logIn = req.logIn || IncomingMessageExt.logIn;
req.logout =
req.logOut = req.logOut || IncomingMessageExt.logOut;
req.isAuthenticated = req.isAuthenticated || IncomingMessageExt.isAuthenticated;
req.isUnauthenticated = req.isUnauthenticated || IncomingMessageExt.isUnauthenticated;
req._sessionManager = passport._sm;
..............
Link to the repo: https://github.com/Gleb-Gaiduk/artwine-api
Any ideas on what could go wrong after cloning the working repository?
You need to transform the ExecutionContext from Graphql to one Nestjs/Passport can read: https://docs.nestjs.com/graphql/other-features#execution-context
import { ExecutionContext, Injectable } from '#nestjs/common';
import { GqlExecutionContext } from '#nestjs/graphql';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class MySuperGuard extends AuthGuard('jwt') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
try adding this into "subscriptions" section of GraphQL module initialization:
'subscriptions-transport-ws': {
onConnect: (headersRaw: Record<string, unknown>) => {
// Lowercase each header key
const headers = Object.keys(headersRaw).reduce((dest, key) => {
dest[key.toLowerCase()] = headersRaw[key];
return dest;
}, {});
return {
req: {
headers: headers,
},
};
},
},
I have no idea why it is not documented, but it worked for me.
You should look your Guard and strategy you are using and should handle errors from there for example:
#Injectable()
export class PassportLocalGuard extends AuthGuard('local') {
protected readonly logger = new Logger(PassportLocalGuard.name);
canActivate(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const { req } = ctx.getContext();
return super.canActivate(new ExecutionContextHost([req]));
}
handleRequest(err: any, user: any) {
if (err) {
this.logger.error(`Auth Error! ${err.message}`);
throw err;
}
if (!user) {
this.logger.error('Auth Error! User not found');
throw new AuthenticationError('Auth Error! User not found');
}
return user;
}
}

Why can't I access `userService` variable after express instance running in Nodejs

I was trying to create an endpoint in node.js, more specifically in express
but I am not sure why I can't access userService variable when requesting from a client.
I've gotten Cannot read property 'userService' of undefined, but when i move ServicesFactory.getInstance().getUserService() inside the signUp function it works?!
I am guessing that node.js garbage collects it due to it's not being used until the user make a request.
export class UserApi implements WebEndpoint {
router: Router
userService = ServicesFactory.getInstance().getUserService()
constructor() {
this.router = Router()
this.router.post('/signup', this.signUp)
}
signUp(req: Request, res: Response): void {
const user: User = req.body
this.userService.signUp(user)
res.send("Successfully registered")
}
}
I found the problem, so basically I am a noob.
consider this example
class a {
constructor() {
this.a1 = 'hello';
}
greet(){
const greeting = `${this.a1} dude!`;
console.log(greeting);
};
}
class b {
b1 = new a();
constructor() {
this.b1.greet.call();
}
}
new b();
Now it wouldn't run, because b class called greet method with a new context, the same with express when you provide a function as a handler on an Express endpoint it will be called with a new set of context (read:this) that's why this.userService in my code above won't work because there is no userService property in the context provided by Express.
The solution is simple. Arrow function.
signUp = (req: Request, res: Response): void => {
const user: User = req.body
this.userService.signUp(user)
res.send("Successfully registered")
}
Now the function will inherit it's class's context.You can refer to this for more detail answer.

TypeScript in Node with Express. Cannot set a controller's class property with a constructor

I have a Node app with Express implementing TypeScript using Babel.
I tried creating a UserController with a private property _user: User and set it in the class constructor. The problem is, I keep getting "message": "Cannot read property '_user' of undefined" in the output.
This is my class:
export class UserController extends CrudController {
private _user: User;
constructor() {
super();
const user = new User();
user.id = 1;
user.email = 'johnappleseed#apple.com';
user.firstName = 'John';
user.lastName = 'Appleseed';
this._user = user; // Setting the global class property
}
get(req: Request, res: Response) {
res.json({ user: this._user });
}
}
The extended class CrudController is just an abstract class, but still, here's the code:
export abstract class CrudController {
public abstract get(req: Request, res: Response): void;
}
This is my routes file:
const userController = new UserController();
router.route('/user')
.get(userController.get);
What could be the issue? Thanks!
Since your passing userController.get to the router, you will loose the this-context of the class, once the router is invoked.
You can solve this by binding the method to userController and thus setting this to the controller (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind for more information):
const userController = new UserController();
router.route('/user')
.get(userController.get.bind(userController);

How to execute pipe(ValidateObjectId) before guard(ResourceOwnerGuard)?

Im playing around with nestjs and mongoose.
The code:
class BrevesController {
constructor(private readonly brevesService: BrevesService) { }
// Here is used BreveOwnerGuard(1)
#UseGuards(JwtAuthGuard, BreveOwnerGuard)
#Get(':breveId')
// Here is used ValidateObjectId(3)
async getById(#Param('breveId', ValidateObjectId) id: string) {
return await this.brevesService.getById(id)
}
}
class BreveOwnerGuard {
constructor(private readonly brevesService: BrevesService) { }
async canActivate(context: ExecutionContext) {
const req = context.switchToHttp().getRequest()
const {user, params} = req
const {breveId} = params
// This is executed before ValidateObjectId in getById
// route handler and unknown error is thrown but we
// have pipe for this.(2)
const breve = await this.brevesService.getById(breveId)
const breveCreatorId = breve.creatorId.toString()
const userId = user.id
return breveCreatorId === userId
}
}
So after request /breves/:breveId with invalid object id, the BreveOwnerGuard is executed before ValidateObjectId and unknown error is thrown.
Is there a way for this flow to validate the ObjectId before BreveOwnerGuard ?
Or what should I do in this case? What is expected ?
Guards are executed after each middleware, but before any interceptor or pipe.
Source: Guard Docs (emphasis by me)
Not much you can do other than change the ResourceOwnerGuard to a pipe or the ValidateObjectId into a Guard.

typescript + node How to get instanse from methods?

After server rebuild, compiller creates instanse in included api controller here:
NewController.ts
import express = require("express");
import INew = require("../interface/INew");
import NewRepository = require("../repositories/NewRepository");
class NewController {
private _newRepository: INew;
constructor() {
this._newRepository = new NewRepository();
this._newRepository.findById(5);
}
retrieve(req: express.Request, res: express.Response): void {
try {
console.log('-----------retrieve--------------------');
this._newRepository.findById(2);
}
catch (e) {
console.log(e);
}
}
}
Object.seal(NewController);
export = NewController;
constructor works: i see console message:
-------------NewRepository------------------
5 'RESULT'
NewRepository.ts:
import INew = require("../interface/INew");
import bluebird = require("bluebird");
class NewRepository implements INew {
sd: string;
constructor() {
console.log('-------------NewRepository------------------');
}
findById(id: number): void {
setTimeout(function () {
console.log(id, 'RESULT');
}, 3000);
}
}
export = NewRepository;
INew.ts
interface INew {
findById: (id: number) => void;
sd: string;
}
export = INew;
Buut when i use controller's method 'retrieve', visit rout '/new' then i get error [TypeError: Cannot read property '_newRepository' of undefined] instead : 2 'RESULT'
Angular 2 helps me with routing:
.............
getCarsRestful(): Promise<New[]> {
console.log('-------------------------------');
return this.http.get('api/new')
.toPromise()
.then(response => response.json())
.catch(this.handleError);
}
...................
and execute backend:
NewRoutes.ts
import express = require("express");
import NewController = require('../controllers/NewController');
var router = express.Router();
class NewRoutes {
private _newController: NewController;
constructor() {
this._newController = new NewController()
}
get routes() {
var controller = this._newController;
router.get("/new", controller.retrieve);
return router;
}
}
Object.seal(NewRoutes);
export = NewRoutes;
my created instanse '_newRepository' doesn't exist already, why? i get console log:
-----------retrieve--------------------
[TypeError: Cannot read property '_newRepository' of undefined]
Help please, how to make 'singltone' in ts
i don't want to create it in every controller's method, though, that works:
.................
retrieve(req: express.Request, res: express.Response): void {
try {
var _newRepository: INew;
_newRepository = new NewRepository();
_newRepository.findById(2);
.............
Try explicitly set this in router config:
router.get("/new", controller.retrieve.bind(controller));

Resources