Node.js Route to Controller not applying Controller constructor - node.js

I have an express.js application that uses the express.Router() to connect my endpoints to controllers.
My goal is to have an object newed up in the controller constructor so I can use it in all controller functions without having to new it up in each one.
The constructor runs correct, and the object is available within the constructor. But whenever I call any actions of the controller, the object is null.
Here is the router
const express = require('express');
const componentController = require('../controllers/component');
const router = express.Router();
// component routes
router.get('/components', componentController.getComponents);
module.exports = router;
And here is my controller.
const LogService = require('../services/logService');
class ComponentController {
constructor() {
this.logger = new LogService('ComponentController');
this.logger.logDebug('test1','test1');
}
async getComponents(req, res) {
const test = new LogService('ComponentController');
test.logDebug('test2','test2');
this.logger.logDebug('test3','test3')
res.json('');
}
}
module.exports = new ComponentController();
I want the LogService to be available in the controller actions. The first two logs work correctly, test1 and test2. But test3 throws an error saying logger is undefined.
Why is this.logger undefined in later functions? How can I fix this issue?

try to refactor getComponents to an arrow function.
Here is why: https://javascript.plainenglish.io/this-binding-in-es6-arrow-function-70d80e216238
You can also do this:
router.get('/components', componentController.getComponents.bind(componentController));

Related

Problem with instance of class while creating reusable controller in node.js

I want to reuse all my servise and controllers and I was achieving this with extending class.
I'm calling the class of Controller and service all the way up from a router.
everything looks fine while creating instance of class,
but when I send request to sign in route it says there is no this.servise or this.dto which is i passed to the constructor while creating this instance of object
import express from "express";
const router = express.Router();
import UserController from "../../controllers/user.controller";
import UserService from "../../services/user.service.js";
import {UserDto} from "../../dtos/user.dto.js";
import User from "../../models/User.js";
const userService = new UserService(User);
console.log(userService, UserDto); // successfully logged
const userController = new UserController(userService, UserDto);
console.log(userController); // successfully logged
router.post('/signup', userController.signup);
router.post('/signin', userController.signIn);
router.post('/signout', userController.signOut);
router.get('/all', userController.getAll);
router.route("/:id")
.get(userController.get)
.post(userController.create)
.patch(userController.update)
.delete(userController.delete);
export default router;
export default class UserController extends MainController {
async signIn(req, res) {
try {
const {email, password} = req.body;//await this.dto.login(req.body);
const result = await this.service.signIn(email, password); // TypeError: Cannot read properties of undefined (reading 'service')
return res.status(201).json({message: "successfully signed in", token: result.token, user: this.dto.output(result.user)});
}
catch(err){
sendError(res, err);
}
}
I've reviewed my knowledge of how nodejs modules work, but I can't figure out what the problem is.
Who can explain me this situation, please!?
Thank you in advance!
You forgot to use .bind() when passing the references to the methods to the Express router. So who knows what the value of this will be when Express tries to call them (in JS, this doesn't work like other variable names, it doesn't use lexical scoping, what the value will be depends on the calling code).
Change your code to e.g.
router.post('/signup', userController.signup.bind(userController));
This will ensure that this is set to your userController object when the function gets called (bind() is a method available on all function objects that returns a new function with a fixed this value).

Access model method inside express route (Loopback 4)

I will show you an example of what i'm trying to do :
server.ts
export class ExpressServer {
public readonly app: express.Application;
public readonly lbApp: ImportedApp;
private server?: Server;
constructor(options: ApplicationConfig = {}) {
this.app = express();
this.lbApp = new ImportedApp(options);
this.app.get('/hello', async function (_req: Request, res: Response) {
//Here i'd like to call a model like User.findById() but can't figure out how to do it..
});
}
}
As you see in the comment i'm trying to access my models method to use them in my route (Like showing users informations on my view) But can't figure out how to do it. I'v already tryed to import the DataSource, the model, the controller but nothing's containing my methods (FindById, Create etc..)
If i find nothing i will have to use something like Axios or Request to request the ressource from the api instead of inside my code like await request('api/users/myusername)
In LoopBack 4, we use Repository design patter for accessing data. In order to find a user instance by its id, you need to obtain an instance of UserRepository via dependency injection. Quoting from https://loopback.io/doc/en/lb4/Repository.html:
Repositories are adding behavior to Models. Models describe the shape of data, Repositories provide behavior like CRUD operations. This is different from LoopBack 3.x where models implement behavior too.
UPDATED SOLUTION
To obtain an instance of a Repository class, you can use the Service Locator design pattern and get the instance from the per-request Context object provided by LoopBack's REST layer.
import {MIDDLEWARE_CONTEXT, RequestContext} from '#loopback/rest';
import {UserRepository} from '../repositories';
function expressHandler(req, res, next) {
const ctx = (req as any)[MIDDLEWARE_CONTEXT];
const userRepo = await ctx.get<UserRepository>('repositories.UserRepository');
const users = await userRepo.find({limit: 10});
// render your view
}
We are discussing how to make this use case easier to implement in GitHub pull request loopback-next#6793, feel free to join the discussion there.
ORIGINAL ANSWER
Instead of writing an Express route for your rendered pages, I recommend you to write a LoopBack 4 Controller instead; and inject Express Response object to allow you to render the HTML view, as explained in https://loopback.io/doc/en/lb4/Accessing-http-request-response.html#inject-http-response
import {Response, RestBindings, oas} from '#loopback/rest';
import {inject} from '#loopback/core';
import {UserRepository} from '../repositories';
export class PingController {
constructor(
#inject(RestBindings.Http.RESPONSE)
private response: Response
#repository(UserRepository)
public userRepository: UserRepository,
) {}
// Hide this endpoint from OpenAPI spec generated for the app
#oas.visibility('undocumented')
#get('/users')
list(): Response {
// Access User data via this.userRepository API
const users = await this.userRepository.find({limit: 10});
// Access the response object via `this.response`
this.response.render('users', {users});
// Return the HTTP response object so that LoopBack framework skips the
// generation of HTTP response
return this.response;
}
}
Having said that, if you already know how to access DataSource instances from your LB4 app in your Express routes, then you can instantiate Repository classes manually from your routes too:
const db = // your datasource
this.app.get('/hello', async function (_req: Request, res: Response) {
const repo = new UserRepository(db);
const users = await this.userRepository.find({limit: 10});
});
To me the solution is not working. Started from the express-composition example, i just need to access lb repositories from a generic express route outside of the lb4 request handler:
constructor(options: ApplicationConfig = {}) {
this.app = express();
this.lbApp = new NoteApplication(options);
this.lbApp.basePath('')
// Expose the front-end assets via Express, not as LB4 route
this.app.use('/api', this.lbApp.requestHandler);
this.app.get('/hello', async (req: Request, res: Response) => {
const ctx = (req as any)[MIDDLEWARE_CONTEXT];
const userRepo = await ctx.get('repositories.UserRepository');
res.send('Hello world!');
});
}
the ctx in the line
const ctx = (req as any)[MIDDLEWARE_CONTEXT];
is always undefined.
My main goal is to have routes not under /api that can still access lb4 repositories.

How do I mock express.Application with Jest?

Some class that takes express as an argument (basic DI):
class App {
constructor(express: express.Application) { /* ... */ }
}
My test:
// this doesn't work ("argument not assignable"):
//const expressMock = jest.mock("express");
//let app = new App(expressMock);
// so how do I mock it?
const expressMock = ???
let app = new App(expressMock);
How do I mock express.Application with Jest? The whole thing, not just a request, or route, etc.
Technically, express.Application cannot be mocked, it's an interface. It's an object that it represents that should be mocked.
As jest.mock documentation states, it
Returns the jest object for chaining.
So this is not a correct way to retrieve mocked object:
const expressMock = jest.mock("express");
A correct one would be
jest.mock("express");
...
const expressMock = require("express");
Moreover, App accepts not express factory function but application object, which is a result of express() call.
jest.mock without factory function results in auto-mocked express factory which won't produce proper application object.
Since expressMock is passed to App directly in tests, there's no necessity to mock Express module. A mock that contains bare minimum implementation can be passed instead:
const expressMock = {
use: jest.fn(),
...
} as any as express.Application;
let app = new App(expressMock);

Express.js: Use Routers inside Routers

I have such router:
exports.read = (req,res) ->
// do stuff with DB
res.status(200).send
data:data
Now how can I use this router inside another router, call it exports.wrapper? I wnat to avoid having to rewrite my DB requests again and again. Is this approach that I have in mind recommended?
I would not recommend attempting to wrap routers inside each other.
It's recommended in Express 4 to use Express's router object like so:
// router.js
var myRouter = express.Router();
myRouter.route('/read', myController.readMethod);
Then in your controller you would handle the request and end with the result call:
// myController.js
exports.readMethod = function(req, res) {
var data = readFromDB(req.params);
res.send('read method renders', data);
}
exports.readMethod2 = function(req, res) {
var data = readFromDB(req.params);
res.send('read method 2 renders', data);
}
function readFromDB(params) {
// make a call to the DB (maybe via a model)
// return some data
}
Hope that helps
edit
Additionally, I would recommend wrapping your DB calls in a model, to abstract them away from your router or controller logic. For a reference of a well organised Express App that uses MVC checkout this Yeoman generator - https://github.com/ngenerio/generator-express-simple
second edit
In my very brief example externalising the readFromDB method to a model makes this function moot, if all it's doing is getting data from the DB put it in a model.

Unit testing express routers

I know this has been discussed a couple of times. Anyway, I feel like all the provided solutions don't (perfectly) fit to my requirement(s). I have the following code:
router.js:
------------------
var Router = function(app, resourceName, controller) {
//Create
app.post('/api/' + resourceName, function(req, res) {
console.log('Incoming request: ' + resourceName + ' (POST)');
controller.create(req, res);
});
};
module.exports = Router;
As you can see this is a very "generic" router. It can be instantiated for example in the server like this:
var app = express();
var userController = ...
var userRouter = new Router(app, 'Users', userController);
So I don't have to write a file per resource but I just have one generic router.
I would like to test my generic router but I see some problems:
How to "inject" the app? I could create an instance of Express (var app = express();) but I think a mock would be better (as this is a unit test, not an integration test!). What's the best way to get an appropriate mock?
What exactly should I test? As far as I see my router itself (without integration) isn't doing anything else but console output (not worth to test) and a call of a function (controller.create(req, res);). How should I test if this function is called? Or is there anything else to test?
You should probably make a stub implementation of app.
What you want to test is that the constructor registers listeners on specified routes + HTTP methods. I would advise putting Sinon.js stubs into your app stub, and then in your tests check that they are called with expected arguments.
I would use jasmine.createSpyObj to mock app (and maybe controller as well).
I think you just need to test that app.post gets called with the arguments '/api/' + resourceName and controller.create, because you aren't testing that express.post works correctly or not.
Here's how I'd do those two things specifically.
I'd modify router.js a little bit to make this easier:
var Router = function(app, resourceName, controller) {
app.post('/api/' + resourceName, controller.create.bind(controller))
}
module.exports = Router;
And then the test would look like this:
describe("Router", function() {
it("should route /api to controller.create", function() {
router = require('./router');
app = jasmine.createSpyObj('application', ['post']);
controller = jasmine.createSpyObj('controller', ['create']);
router(app, 'foo', controller);
expect(app.post).toHaveBeenCalledWith('/api/foo', jasmine.any(Function));
});
});
This isn't a perfect test because it isn't actually checking that controller.create specifically is getting called. That gets a little more complicated because of the .bind() stuff.
describe("Router", function() {
it("should route /api to controller.create", function() {
router = require('./router');
app = jasmine.createSpyObj('application', ['post']);
controller = jasmine.createSpyObj('controller', ['create']);
controller.create = jasmine.createSpyObj('binder', ['bind']);
controller.create.bind.and.returnValue('bar');
router(app, 'foo', controller);
expect(controller.create.bind).toHaveBeenCalledWith(controller);
expect(app.post).toHaveBeenCalledWith('/api/foo', controller.create.bind(controller));
});
});

Resources