Express - REST API - repository instance is lost during routing - node.js

please can you help me with my routing problem? I'm trying to make a REST API using typescript and repository pattern in Node.js (express).
I have two generic classes BaseRepository and BaseController which together handle the basic CRUD transactions. Other domain controllers are derived from these ones.
There is my code:
productRouter.ts used to handle routes:
import { Router } from 'express';
import { ProductController } from '../controllers/ProductController';
class ProductRouter {
private _router: Router;
private _controller: ProductController;
constructor() {
this._router = Router();
this._controller = new ProductController;
}
get routes(): Router {
this._router.get('/product', this._controller.getAll);
this._router.post('/product', this._controller.create);
this._router.get('/product/:id', this._controller.getById);
this._router.put('/product/:id', this._controller.update);
this._router.delete('/product/:id', this._controller.delete);
return this._router;
}
}
productController.ts used to init the BaseRepository and its derived from the BaseController.
import { BaseController } from '../common/BaseController';
import { BaseRepository, IRepository } from '../common/BaseRepository';
import { Product, IProduct } from '../models/Product';
export class ProductController extends BaseController<IProduct> {
constructor() {
const productRepository = new BaseRepository<IProduct>(Product);
super(productRepository);
}
}
BaseController.ts
export class BaseController<T> implements IController {
private _repository: IRepository<T>;
constructor(repository: IRepository<T>) {
this._repository = repository;
}
getAll(req: Request, res: Response, next: NextFunction): void {
this._repository.getAll((err, result) => {
if (err)
res.send(err);
res.json(result);
});
}
Every time I navigate to appropriate route I get the following error:
TypeError: Cannot read property '_repository' of undefined
at BaseController.getAll (C:\Users\vrbat\Documents\Projects\router-test\src\api\common\BaseController.ts:19:13)
enter code here
I don't know why, because the _repository property is iniciated in productController.ts.

After some digging I figured out that the problem is in the wrong scoping of _repository property.
The following code will fix it:
productRouter.ts
get routes(): Router {
this._router.get('/product', this._controller.getAll());
this._router.post('/product', this._controller.create());
this._router.get('/product/:id', this._controller.getById());
this._router.put('/product/:id', this._controller.update());
this._router.delete('/product/:id', this._controller.delete());
return this._router;
}
BaseController.ts
export class BaseController<T> implements IController {
private _repository: IRepository<T>;
constructor(repository: IRepository<T>) {
this._repository = repository;
}
getAll() {
const repository = this._repository;
return (req: Request, res: Response, next: NextFunction) => {
repository.getAll((err, result) => {
if (err)
res.send(err);
res.json(result);
});
};
}

Related

Unable to access the this context inside the callback in NestJs

import {ApiTags} from "#nestjs/swagger";
import {Body, Controller, Get, Param, Post, Res,Req} from "#nestjs/common";
import {ReceiptService} from "../service/ReceiptService";
import {ReceiptDto} from "../common/domain/ReceiptDto";
import {Response,Request} from 'express';
import {HttpService} from "#nestjs/axios";
#ApiTags('Receipt')
#Controller({version:'1',path:'Receipt'})
export class ReceiptController{
constructor(private readonly receiptService:ReceiptService,private readonly httpService:HttpService) {
}
#Post()
generateReport(#Body() receiptDto:ReceiptDto){
return this.receiptService.getReceiptData(receiptDto.OrderId);
}
#Get(':OrderId')
async displayReceipt(#Param('OrderId') orderId:number,#Res() res:Response,#Req() req:Request){
const data=await this.receiptService.getReceiptData(orderId);
res.render('OrderReceipt',{orderData:data},function (err,html) {
res.send(html).on('finish',async function (){
const result=await this.httpService.get(`http://localhost:51971/pdfgenerator?url=localhost:4200/v1/Receipt/${orderId}`).bind(this);
console.log(result);
});
});
}
}
UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'get' of undefined Error has been thrown on this.httpService.get() line. I don't know how to solve this problem. Thanks and regards.
Change the function in the res.render callback to an arrow function to preserve the this context of the class.
#ApiTags('Receipt')
#Controller({version:'1',path:'Receipt'})
export class ReceiptController{
constructor(private readonly receiptService:ReceiptService,private readonly httpService:HttpService) {
}
#Post()
generateReport(#Body() receiptDto:ReceiptDto){
return this.receiptService.getReceiptData(receiptDto.OrderId);
}
#Get(':OrderId')
async displayReceipt(#Param('OrderId') orderId:number,#Res() res:Response,#Req() req:Request){
const data=await this.receiptService.getReceiptData(orderId);
res.render('OrderReceipt',{orderData:data}, (err,html) => {
res.send(html).on('finish',async () => {
const result=await this.httpService.get(`http://localhost:51971/pdfgenerator?url=localhost:4200/v1/Receipt/${orderId}`).bind(this);
console.log(result);
});
});
}
}

How to create common class for third-party API requests in NestJS

I am creating NestJS application where I am making third-party API requests. For that I have to write the same thing inside every function in order to get the data.
To make things non-repeating, how can I write on common class that has API request based on GET or POST request and send the response so that I can use that class in every function.
Below is my code:
subscribe.service.ts
#Injectable()
export class SubscribeService {
constructor(#InjectModel('Subscribe') private readonly model:Model<Subscribe>,
#Inject(CACHE_MANAGER) private cacheManager:Cache,
private httpService: HttpService){}
async addSubscriber(subscriberDto:SubscribeDto){
const url = 'https://track.cxipl.com/api/v2/phone-tracking/subscribe';
const headersRequest = {
'content-Type': 'application/json',
'authkey': process.env.AUTHKEY
};
try{
const resp = await this.httpService.post(url,subscriberDto,{ headers: headersRequest }).pipe(
map((response) => {
if(response.data.success == true){
const data = new this.model(subscriberDto);
// return data.save();
const saved = data.save();
if(saved){
const msgSuccess = {
"success":response.data.success,
"status":response.data.data.status
}
return msgSuccess;
}
}
else{
const msgFail = {"success":response.data.success}
return msgFail;
}
}),
);
return resp;
}
catch(err){
return err;
}
}
async getLocation(phoneNumber:PhoneNumber){
try{
const location = await this.cacheManager.get<Coordinates>(phoneNumber.phoneNumber);
if(location){
return location;
}
else{
const resp = await axios.post('https://track.cxipl.com/api/v2/phone-tracking/location',phoneNumber,{headers:{
'content-Type': 'application/json',
'authkey': process.env.AUTHKEY
}});
const msg:Coordinates = {
"location":resp.data.data.location,
"timestamp":resp.data.data.timestamp
}
await this.cacheManager.set<Coordinates>(phoneNumber.phoneNumber,msg, { ttl: 3600 });
return msg;
}
}
catch(err){
console.log(err);
return err;
}
}
}
As in above code in both function addSubscriber() and getLocation() I need to hit the API repeatedly and add request headers again and again is there any way so that I can create one separate class for request and response and utilize in my service.
How can I achieve desired the result?
To create a common class for making third-party API requests in NestJS, you can follow these steps:
Create a new file in your NestJS project to store the common class.
For example, you could create a file called api.service.ts in the
src/common directory.
In the file, create a new class called ApiService that will be responsible for making the API requests. This class should have a
constructor that injects the necessary dependencies, such as the
HttpService provided by NestJS.
import { HttpService, Injectable } from '#nestjs/common';
#Injectable()
export class ApiService {
constructor(private readonly httpService: HttpService) {}
}
Add methods to the ApiService class for each type of API request you want to make. For example, you might have a get() method for making GET requests, a post() method for making POST requests, and so on. Each method should accept the necessary parameters for making the request (such as the URL and any query parameters or request body), and use the HttpService to make the request.
import { HttpService, Injectable } from '#nestjs/common';
#Injectable()
export class ApiService {
constructor(private readonly httpService: HttpService) {}
async get(url: string, params?: object): Promise<any> {
return this.httpService.get(url, { params }).toPromise();
}
async post(url: string, body: object): Promise<any> {
return this.httpService.post(url, body).toPromise();
}
}
Inject the ApiService wherever you need to make API requests. For example, you might inject it into a service or a controller, and use the methods of the ApiService to make the actual API requests.
import { Injectable } from '#nestjs/common';
import { ApiService } from './api.service';
#Injectable()
export class SomeService {
constructor(private readonly apiService: ApiService) {}
async getData(): Promise<any> {
return this.apiService.get('https://some-api.com/endpoint');
}
}
This is just one way you could create a common class for making third-party API requests in NestJS. You can customize the ApiService class to meet the specific needs of your application

How to get name of module, which controller processing request?

I want to get name of module, which controller processing request.
#Get('/')
getIndex() {
console.log('name of module');
}
I don't know exactly the purpose behind your question, but I'll leave you some alternatives.
First one and the dirtier. You can get the instance of the module that your productController is imported by finding it into the modules container.
import { Controller, Get, Query } from '#nestjs/common';
import { ModulesContainer } from '#nestjs/core';
import { Module } from '#nestjs/core/injector/module';
#Controller('path')
export class ProductController {
constructor(
private modulesContainer: ModulesContainer,
private productService: ProductService
) { }
#Get()
findAll(#Query() dto: any) {
let yourModule: Module;
this.modulesContainer.forEach((v) => {
if(v.controllers.has(this.constructor.name)) { // in this condition, you will find your module based in the imports from it, if your controller is importe in some module it will get the module and put in "yourModule" variable.
// Here
yourModule= v;
}
});
console.log(yourModule);
return this.productService.findAll();
}
}
And for a cleaner approach you can get the moduleRef in your controller
import { Controller, Get, Query } from '#nestjs/common';
import { ModuleRef} from '#nestjs/core';
#Controller('path')
export class ProductController {
constructor(
private moduleRef: ModuleRef,
private productService: ProductService
) { }
#Get()
findAll(#Query() dto: any) {
console.log(this.moduleRef) //your module ref
return this.productService.findAll();
}
}
But of course depends on what's you're trying to do.

Node.js Express API with TypeScript 3 Update a record

I have setup Node.js Express API with TypeScript 3 and it is working fine.
I got an issue when I try to update the record.
RecordsRouter.ts
import { Router } from 'express';
import {RecordComponent} from '../../components';
const router: Router = Router();
router.get('/', RecordComponent.findAll);
router.get('/:id', RecordComponent.findOne);
router.post('/', RecordComponent.create);
router.delete('/:id', RecordComponent.remove);
router.put('/:id', RecordComponent.update);
export default router;
My RecordComponent.ts
export async function update(req: Request, res: Response, next: NextFunction): Promise <void> {
try {
const record: IRecord = await RecordService.put(req.body)
res.status(201).json(record);
} catch (error) {
next(new HttpError(error.message.status, error.message));
}
}
and my IRepository.ts
export interface IRepository<T> {
findAll(): Promise<T[]>;
findOne(code: string): Promise<T>;
insert(T: any): Promise<T>;
remove(id: string): Promise<T>;
put:(T: any)=>Promise<T>;
}
Service.ts
async put(data: IRecord): Promise {
try {
const validate: Joi.ValidationResult = RecordValidation.updateRecord(data);
if(validate.error) {
throw new Error(validate.error.message);
}
return await RecordModel.findOneAndUpdate(data);
} catch (error) {
throw new Error(error.message);
}
},
Did I did all correctly or something is missing because I am getting the error
That means you didn't implement all of the interface members in RecordService. Either implement them or mark them as optional in the IRepository interface by adding a question mark before the colon:
export interface IRepository<T> {
findAll()?: Promise<T[]>;
findOne(code: string)?: Promise<T>;
insert(T: any)?: Promise<T>;
remove(id: string)?: Promise<T>;
put(id: string)?: Promise<T>;
}
So, You should implement RecordService in next way:
const RecordService:IRepository<IRecord>={
// ...some code for findAll, findOne ....
remove:(id: string)=>Promise.resolve(),
put:(id: string)=>Promise.resolve(),
}
UPDATE
Your RecordModel.findOneAndUpdate(data) should receive 0 arguments:
RecordModel.findOneAndUpdate() or RecordModel.findOneAndUpdate(data, other, another)
Please get familiar with mongoose docs

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