I am trying to use inversify for DI injection in a small project in typescript. I wanted to create a logging middleware that logs every request with a pino logger customized. I did establish the dependencies manually when the app is created but was wooed by inversify and wanted to try it out.
But I am stuck and unable to proceed. THis is the error that I am facing. Not sure what is making the call to exceed the stack size. Please help me out
public set app_port(app_port: number) {
^
RangeError: Maximum call stack size exceeded
PinoLoggingService.ts
import * as pino from 'pino';
import * as os from 'os';
import * as momentTimeZone from 'moment-timezone';
import { DEFAULT_TIMEZONE, DEFAULT_TIME_FORMAT } from '../../shared/constants/app.constants';
import { AppConfigModel } from './../../shared/model/app.config.model';
import { isNullOrUndefined } from 'util';
import httpContext = require('express-http-context');
import { injectable, inject } from 'inversify';
import { TYPES } from '../../shared/constants/app.types';
import { AppConfigService } from '../../shared/service/app.config.service';
#injectable()
export class PinoLoggingService {
private appConfig: AppConfigModel;
constructor(#inject(TYPES.AppConfigService) private appConfigService: AppConfigService){
this.appConfig = appConfigService.appConfigModel;
}
private getTimeStamp(){
return momentTimeZone().tz(DEFAULT_TIMEZONE).format(DEFAULT_TIME_FORMAT);
}
public infoLogService (fileName): pino.Logger {
return pino({
level: 'info',
name: this.appConfig.app_name,
messageKey: 'feedback-Logs',
base: {pid: process.pid, hostname: os.hostname,
timestamp: this.getTimeStamp(),
appName: this.appConfig.app_name,
fileName: fileName,
request_id: isNullOrUndefined(httpContext.get('reqId')) ? 'Not an actual request ' : httpContext.get('reqId')
},
enabled: true,
useLevelLabels: true,
});
}
}
my AppConfigService.ts - AppConfigModel is just a plain ts with getters and setters.
#injectable()
export class AppConfigService implements IAppConfigService{
private appModel: AppConfigModel;
constructor() {
console.log('parg is:'+path.resolve(__dirname));
}
public get appConfigModel(): AppConfigModel {
if(isNullOrUndefined(this.appModel)) {
this.appModel = new AppConfigModel();
this.appModel.app_port = isNullOrUndefined(process.env.PORT) ? DEFAULT_PORT : parseInt(process.env.PORT);
this.appModel.app_lcp = process.env.LCP;
this.appModel.app_name = process.env.APP_NAME;
this.appModel.app_host = process.env.HOST;
this.appModel.app_node_env = process.env.NODE_ENV;
this.appModel.app_logging = JSON.parse (process.env.PINO_ENABLE_LOGGING);
this.appModel.app_version = process.env.VERSION;
this.appModel.app_context_path = process.env.CONTEXT_PATH;
}
return this.appModel;;
}
}
This is my loggingMiddleware.ts
import * as express from 'express';
import { AppUtilService } from './../util/app.util.service';
import { file } from '#babel/types';
import { inject, injectable } from 'inversify';
import { TYPES } from '../constants/app.types';
import { PinoLoggingService } from '../../logging/service/logging.service';
interface ILoggingMiddleware {
loggingMiddlewareMethod () : express.Router;
}
#injectable()
export class LoggingMiddleware implements ILoggingMiddleware {
constructor(#inject(TYPES.AppUtilService) private utilService: AppUtilService,
#inject(TYPES.PinoLoggingService) private pinoLoggingService: PinoLoggingService){
}
public loggingMiddlewareMethod() {
const appRouter = express.Router();
return appRouter.use((req:express.Request, res:express.Response, next) => {
const fileName = this.utilService.getFileName(__filename);
this.pinoLoggingService.infoLogService(fileName).info('req.url:', req.url);
this.pinoLoggingService.infoLogService(fileName).info('req.headers:', req.headers);
this.pinoLoggingService.infoLogService(fileName).info('req.body:', req.body);
next();
});
}
}
THis is my request tracing middleware
const httpContext = require('express-http-context');
import * as express from 'express';
import { injectable } from 'inversify';
#injectable()
export class RequestTracerMiddleware {
public requestTracer() {
const app = express();
app.use(httpContext.middleware);
}
}
THis is my inversify config
import { Container } from "inversify";
import { TYPES } from "./module/shared/constants/app.types";
import { AppConfigService } from "./module/shared/service/app.config.service";
import { AppUtilService } from "./module/shared/util/app.util.service";
import { LoggingMiddleware } from "./module/shared/middlewares/logging.middleware";
import { CommonMiddleware } from "./module/shared/middlewares/common.middlewares";
import { RequestTracerMiddleware } from "./module/shared/middlewares/request.tracer.middleware";
import { PinoLoggingService } from "./module/logging/service/logging.service";
import { StudentController } from "./module/shared/test.controller";
const DIContainer = new Container({defaultScope: "Singleton"});
DIContainer.bind<AppConfigService>(TYPES.AppConfigService).to(AppConfigService);
DIContainer.bind<AppUtilService>(TYPES.AppUtilService).to(AppUtilService);
DIContainer.bind<LoggingMiddleware>(TYPES.LoggingMiddleware).to(LoggingMiddleware);
DIContainer.bind<CommonMiddleware>(TYPES.CommonMiddleware).to(CommonMiddleware);
DIContainer.bind<RequestTracerMiddleware>(TYPES.RequestTracerMiddleware).to(RequestTracerMiddleware);
DIContainer.bind<PinoLoggingService>(TYPES.PinoLoggingService).to(PinoLoggingService);
DIContainer.bind<StudentController>(TYPES.StudentController).to(StudentController);
export default DIContainer;
This is my server.ts
import 'reflect-metadata';
import * as DIContainer from './inversify.config';
import {InversifyExpressServer } from 'inversify-express-utils'
import { inject } from 'inversify';
import { TYPES } from './module/shared/constants/app.types';
import { CommonMiddleware } from './module/shared/middlewares/common.middlewares';
import { LoggingMiddleware } from './module/shared/middlewares/logging.middleware';
import { RequestTracerMiddleware } from './module/shared/middlewares/request.tracer.middleware';
const container = DIContainer.default;
let server = new InversifyExpressServer(container);
const commMiddleware = container.resolve(CommonMiddleware);
// const requestTracingMiddleware = container.resolve(RequestTracerMiddleware);
const loggingMiddleware = container.resolve(LoggingMiddleware);
server.setConfig(appRouter => {
appRouter.use(commMiddleware.enableBodyParser());
appRouter.use(loggingMiddleware.loggingMiddlewareMethod());
});
// set errorConfig if needed
const app = server.build();
app.listen(2000, () => {
console.log('listening to port');
});
This is a sample controller that I setup.
import {controller, httpGet, httpPost, requestHeaders, requestParam, BaseHttpController, HttpResponseMessage, StringContent} from 'inversify-express-utils';
#controller("/IStudentController")
export class StudentController extends BaseHttpController {
constructor( ) {
super();
console.log('value of stdservuce:');
}
#httpGet("/Iget")
public getStudentDetails(#requestHeaders() reqHeaders: string[] ) {
// sample returning json resuilt - IHttpAcctionResults
const val = 43;
console.log('val from the servuc:', val);
return this.json(val, 200);
}
}
Related
Getting errors while using fresh-tabula-js with nestJs.
Express server
import Tabula from "fresh-tabula-js";
const table = new Tabula("t5.pdf", { spreadsheet: true });
console.log(table.extractCsv().output);
NestJS
1) code-1
import { Injectable } from '#nestjs/common';
import Tabula = require('fresh-tabula-js');
#Injectable()
export class PdfExtractorService {
tabula: Tabula;
constructor() {
this.tabula = new Tabula('t5.pdf', { spreadsheet: true });
}
extractPdfToJson() {
const data: Promise<{ output: string; error: string }> =
this.tabula.getData();
console.log(data);
}
}
Error:
this.tabula.getData is not a function
2) code-2
import { Injectable } from '#nestjs/common';
import Tabula = require('fresh-tabula-js');
#Injectable()
export class PdfExtractorService {
tabula: Tabula;
constructor() {
this.tabula = new Tabula('t5.pdf', { spreadsheet: true });
}
extractPdfToJson() {
const data: Promise<{ output: string; error: string }> =
this.tabula.extractCsv().output;
console.log(data);
}
}
Error:
error TS2339: Property 'extractCsv' does not exist on type 'Tabula'.
this.tabula.extractCsv().output;
Tried all possible ways but it is not working with nestJs however it is working with expressJs.
How to make it run in nestJS?
I am using express and tsyringe for dependency injections.
When I try to use the service dependency on my controller I get an error UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'addressService' of undefined in file addressController.ts
I've searched for several videos and articles and I couldn't find a way to solve them. I will leave my code below
addressRepository.ts
import { AddressResponseDTO } from '../../dtos/response/address/addressResponseDTO';
export interface IAddressRepository {
getAddresses(): Array<AddressResponseDTO>;
}
addressRepositoryImpl.ts
import { AddressResponseDTO } from '../../dtos/response/address/addressResponseDTO';
import { IAddressRepository } from '../declarations/addressRepository';
export default class AddressRepositoryImpl implements IAddressRepository {
getAddresses(): Array<AddressResponseDTO> {
const address1: AddressResponseDTO = {
id: 'ADR123',
postalCode: '17014273',
address: 'Rua José Aiello',
district: 'Centro',
number: '347',
city: 'Bauru',
state: 'São Paulo',
phoneNumber: '16997102842',
clientName: 'João Mário Marcelo Campos',
};
const address2: AddressResponseDTO = {
id: 'ADR456',
postalCode: '07135290',
address: 'Rua Mariano Moya Peramos',
district: 'Jardim Adriana',
number: '1103',
city: 'Guarulhos',
state: 'São Paulo',
phoneNumber: '16997102842',
clientName: 'João Mário Marcelo Campos',
};
return [address1, address2];
}
}
addressService.ts
import { AddressResponseDTO } from '../../dtos/response/address/addressResponseDTO';
import ResultResponseDTO from '../../dtos/response/resultResponseDTO';
export interface IAddressService {
getAddresses(): ResultResponseDTO<Array<AddressResponseDTO>>;
}
addressServiceImpl.ts
import { StatusCodes } from 'http-status-codes';
import { inject, injectable } from 'tsyringe';
import logger from '../../configurations/logger';
import { AddressResponseDTO } from '../../dtos/response/address/addressResponseDTO';
import ResultResponseDTO from '../../dtos/response/resultResponseDTO';
import { IAddressRepository } from '../../repositories/declarations/addressRepository';
import { IAddressService } from '../declarations/addressService';
#injectable()
export default class AddressServiceImpl implements IAddressService {
private addressRepository: IAddressRepository;
constructor(#inject('AddressRepository') addressRepository: IAddressRepository) {
this.addressRepository = addressRepository;
}
getAddresses(): ResultResponseDTO<AddressResponseDTO[]> {
try {
logger.info('AddressService.getAddresses - start');
const addresses = this.addressRepository.getAddresses();
logger.info('AddressService.getAddresses - end');
return ResultResponseDTO.ok(StatusCodes.OK, '', addresses);
} catch (error) {
logger.error(`AddressService.getAddresses - error - message ${error}`);
return ResultResponseDTO.fail(
StatusCodes.INTERNAL_SERVER_ERROR,
'Error getting addresses',
error,
);
}
}
}
addressController.ts
Error at line: const result = this.addressService.getAddresses();
import { Request, Response } from 'express';
import { inject, injectable } from 'tsyringe';
import { IAddressService } from '../services/declarations/addressService';
#injectable()
export class AddressController {
constructor(#inject('AddressService') private addressService: IAddressService) {}
public async getAddresses(request: Request, response: Response): Promise<Response> {
const result = this.addressService.getAddresses();
return response.status(result.httpStatus).json(result.getResponse());
}
}
routes.ts
import { Router } from 'express';
import { container } from 'tsyringe';
import { AddressController } from './controllers/addressController';
import AddressRepositoryImpl from './repositories/implementations/addressRepositoryImpl';
import AddressServiceImpl from './services/implementations/addressServiceImpl';
const routes = Router();
container
.register('AddressRepository', {
useClass: AddressRepositoryImpl,
})
.resolve(AddressRepositoryImpl);
container
.register('AddressService', {
useClass: AddressServiceImpl,
})
.resolve(AddressServiceImpl);
const addressController = container.resolve(AddressController);
routes.get('/', (request, response) => {
response.send('It works!');
});
// AddressController
routes.route('/v1/address').get(addressController.getAddresses);
export default routes;
You register service inside IoC as 'AddressServiceImpl' and after that try to call to it as 'AddressService'.
// service file
#injectable()
export default class AddressServiceImpl implements IAddressService {
...
// controller file
#injectable()
export class AddressController {
constructor(#inject('AddressService') private addressService: IAddressService) {}
I have a strange behaviour on an endpoint in NestJS serving a piece of JSON.
The JS with the JSON object is exporting
module.exports = Object.freeze({
translation: {
TestMessage: 'Bienvenue à React et react-i18next'
}
});
The result on the Client is:
{
"translation": {
"TestMessage": "Bienvenue à React et react-i18next"
},
"default": {
"translation": {
"TestMessage": "Bienvenue à React et react-i18next"
}
}
}
The question is where is the "default" coming from?
To paint the whole picture, below the module, controller and service:
Module
import { Module } from '#nestjs/common';
import { LoggerService } from '#modules/logger';
import { I18nController } from './i18n.controller';
import { I18nService } from './i18n.service';
#Module({
controllers: [I18nController],
providers: [I18nService, LoggerService],
exports: [I18nService]
})
export class I18nModule {}
Controller
import { Controller, Get, Param } from '#nestjs/common';
import { LoggerService } from '#modules/logger';
import { I18nService } from './i18n.service';
#Controller('i18n')
export class I18nController {
constructor(private logger: LoggerService, private i18nService: I18nService) {
this.logger.setContext(I18nController.name);
}
#Get('/:lang')
async getLanguage(#Param('lang') lang: string) {
console.log(lang);
return await this.i18nService.findOneByLanguageCode(lang);
}
}
Service
import { Injectable } from '#nestjs/common';
import { access } from 'fs/promises';
import { constants as fsconstants } from 'fs';
#Injectable()
export class I18nService {
async findOneByLanguageCode(language: string): Promise<any | null> {
const languagefile = __dirname + '/../../public/languages/' + language + '.js';
await access(languagefile, fsconstants.R_OK);
return await import(languagefile);
}
}
From the Client I do a simple http://localhost:3001/i18n/fr-FR
and get the above result.
Again, where is the 'default' section coming from?
There should be esModuleInterop enabled in your tsconfig.json
https://www.typescriptlang.org/tsconfig#esModuleInterop
a default import like import moment from "moment" acts the same as const moment = require("moment").default
That's why you have default object exist.
tsconfig.json
{
"compilerOptions": {
"esModuleInterop": true, // change it to false or remove it
}
}
I have an async function getIdentByInfo and in the console i get the right output if i log it in this function. As soon as i call it in another component it doesnt work and i only get 'undefined'. I know it has something to do with beeing ssynchrone and Promises but i cant figure out how to solve my issue. I need the Model class filled with attributes coming from the http request in another component to send them to another service
import { EventEmitter, Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { IdentModel } from "../models/identmodel.model";
import { IdentteilComponent } from "../pages/identteil/identteil.component";
#Injectable({
providedIn: 'root',
})
export class InfoWebservice {
url = 'http://localhost:4201';
ident: IdentModel[];
constructor(private http: HttpClient) { }
// promise vom typ IdentModel zurückgeben
getIdentByInfo(id: string, vwk: string) {
this.http.get(this.url).toPromise().then(data => {
for (let i in data){
this.ident.push(data[i])
if ( this.ident[i].identNr == id && this.ident[i].vwk == vwk){
return this.ident[i];
}
}
});
}
}
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { InfoWebservice } from '../../webservices/info.webservice'
import { ImageWebservice } from '../../webservices/image.webservice'
import { IdentModel } from "../../models/identmodel.model";
#Component({
selector: 'app-identteil',
templateUrl: './identteil.component.html',
styleUrls: ['./identteil.component.scss']
})
export class IdentteilComponent implements OnInit {
ident = [];
identNr:string;
vwk:string;
imgFrontLink:string;
imgBackLink:string;
constructor(private router: Router, private service: InfoWebservice, private image: ImageWebservice) { }
getIdentNr() : string {
var split = this.router.url.split("/");
this.identNr = split[2];
return this.identNr;
}
//return type is STRING
getVwk() {
// output von window.location.host = repapp-maw.dbl.de
// var splitHost = window.location.host.split(".");
var splitHost = 'repapp-maw';
var splitV = splitHost.split("-");
this.vwk = splitV[1];
return this.vwk;
}
callInfoService = async () => {
return await this.service.getIdentByInfo(this.getIdentNr(), this.getVwk());
}
ngOnInit() {
console.log(this.callInfoService());
}
}
When you use angular, its always preferred not to use await/Promise. Angular has an in-built RX-JS library which has tonnes of super-awesome functionalities that you can use.
For Example, in your case, you can do something like this:
// Your Service File can make use of 'Behavior Subject'
// Please read more about it here: https://www.learnrxjs.io/learn-rxjs/subjects/behaviorsubject
import { EventEmitter, Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { IdentModel } from "../models/identmodel.model";
import { IdentteilComponent } from "../pages/identteil/identteil.component";
#Injectable({
providedIn: 'root',
})
export class InfoWebservice {
url = 'http://localhost:4201';
ident: IdentModel[];
initialIdentValues: IdentModel = [];
private identSource: BehaviorSubject<IdentModel[]> = new BehaviorSubject<IdentModel[]>(this.initialIdentValues);
public identValuesObs$: Observable<IdentModel[]> = this.identSource.asObservable();
// Create a method to set the values in component-1
setIdentValues(identValues: IdentModel[]) {
this.identSource.next(identValues);
}
// Create a method to return values in component-2 or any component
returnIdentValues() {
return this.identValuesObs$;
}
constructor(private http: HttpClient) { }
// Change your service call to this:
getIdentByInfo(id: string, vwk: string): Observable<any> {
return this.http.get(this.url);
}
}
Now in your component-1 where you want to set the values of this identvalues:
// Component-1
constructor(private infoWebService: InfoWebService){}
// Create a method where you get the values
someMethod() {
// Call the API method here and subscribe and then set the values
this.infoWebService.getIdentInfoById(id, vwk).subscribe((data: any) => {
// Your logic goes here ANDD
if (data) {
for (let i in data){
this.ident.push(data[i])
let localIdentsWithRequiredLogic = [];
if ( this.ident[i].identNr == id && this.ident[i].vwk == vwk){
localIdentsWithRequiredLogic.push(this.ident[i]);
}
// THIS IS IMPORTANT
this.infoWebService.setIdentValues(localIdentsWithRequiredLogic);
}
}
})
}
Then in component-2 or whatever component you want, you can retrieve it using the returnIdentValues method like this:
// In component-2
inSomeMethodWhereYouRequireIdentValues() {
this.infoWebService.returnIdentValues().subscribe(data => {
console.log(data) // this is data that you set in component one
})
}
I have a problem in .take() operator after they change to AngulaFireObject
and preserveSnapshot.
What can be the problem?
import { Injectable } from '#angular/core';
import { AngularFireDatabase, AngularFireObject } from 'angularfire2/database';
import { User } from 'firebase/app';
import { Profile } from '../../models/profile/profile.interface';
import { Observable } from 'rxjs/observable';
import { take } from 'rxjs/operator/take';
#Injectable()
export class DataService {
profileObject : AngularFireObject<Profile>
constructor(private database: AngularFireDatabase) {
}
getProfile(user: User) {
this.profileObject = this.database.object(`/profiles/${user.uid}`, { preserveSnapshot: true }); <<<in here
return this.profileObject.take(1); <<<< and here
}
async saveProfile(user: User, profile: Profile) {
this.profileObject = this.database.object(`/profiles/${user.uid}`);
When I run my app with ionic serve it gives me this
Typescript Error Expected 1 arguments, but got 2. C:/Users/A7MD/Desktop/WallPapa/src/providers/data/data.service.ts getProfile(user: User) { this.profileObject = this.database.object(/profiles/${user.uid}, { preserveSnapshot: true }); –
and
Typescript Error Property 'take' does not exist on type 'AngularFireObject<Profile>'. C:/Users/A7MD/Desktop/WallPapa/src/providers/data/data.service.ts return this.profileObject.take(1); }
You can see the photo here: