I am pretty new in the NodeJS but I would like to learn something new. I came from .NET fancy dependency injection, inversion of controll, microservice shiny world so I am trying write some service in TypeScript based on my previous experiences.
I am using express and express router to create some api. I have some methods in router which handles api calls and I want to use some kind of service object for data retrieving and manipulation.
I inject the service into the router using constructor injection but if I want to use my service it throws an error:
TypeError: Cannot read property 'layoutService' of undefined
I understood that the methods were called withouth context so I added .bind(this) to the each method regsitration and it works, but I dont know if it is the best way how to do it.
Does anyone have a better idea?
simplified server.ts
import express, { Router } from "express";
// inversion of controll
import container from "./ioc";
import { TYPE } from "./constants";
import IMyService from "./abstract/IMyService";
// import routers
import MyRouter from "./api/MyRouter";
app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const router: Router = express.Router();
const myRouter: MyRouter = new MyRouter(container.get<IMyService>(TYPE.IMyService));
app.use("/", router);
app.use("/api/v1/layouts", layoutRouter.router);
MyRouter.ts
import IMyService from "./abstract/IMyService";
import { Router, Request, Response } from "express";
import { inject } from "inversify";
import { TYPE } from "../constants";
export default class MyRouter {
public readonly router: Router;
private readonly myService: IMyService;
constructor(
#inject(TYPE.IMyService) myService: IMyService
) {
this.myService = myService;
this.router = Router();
this.routes();
}
public GetAll(req: Request, res: Response): void {
this.myService.getAll()
.then(data => {
const status: number = res.statusCode;
res.json({ status, data });
})
.catch(err => {
const status: number = res.statusCode;
res.json({ status, err });
});
}
public GetOne(req: Request, res: Response): void {
const id: string = req.params.id;
this.myService.getOne(new ObjectID(id))
.then(data => {
const status: number = res.statusCode;
res.json({ status, data });
})
.catch(err => {
const status: number = res.statusCode;
res.json({ status, err });
});
}
routes(): void {
this.router
.get("/", this.GetAll)
.get("/:id", this.GetOne);
}
}
If you define your function with the arrow syntax (ES6), it will "bind" the context to it automatically and you won't need to bind them. But it will depends on your use case (ou might need to bind a different context)
Related
I have the multipart form to be validated before file upload in nestjs application. the thing is that I don't want the file to be uploaded if validation of body fails.
here is how I wrote the code for.
// User controller method for create user with upload image
#Post()
#UseInterceptors(FileInterceptor('image'))
create(
#Body() userInput: CreateUserDto,
#UploadedFile(
new ParseFilePipe({
validators: [
// some validator here
]
})
) image: Express.Multer.File,
) {
return this.userService.create({ ...userInput, image: image.path });
}
Tried so many ways to turn around this issue, but didn't reach to any solution
Interceptors run before pipes do, so there's no way to make the saving of the file not happen unless you manage that yourself in your service. However, another option could be a custom exception filter that unlinks the file on error so that you don't have to worry about it post-upload
This is how I created the whole filter
import { isArray } from 'lodash';
import {
ExceptionFilter,
Catch,
ArgumentsHost,
BadRequestException,
} from '#nestjs/common';
import { Request, Response } from 'express';
import * as fs from 'fs';
#Catch(BadRequestException)
export class DeleteFileOnErrorFilter implements ExceptionFilter {
catch(exception: BadRequestException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
const getFiles = (files: Express.Multer.File[] | unknown | undefined) => {
if (!files) return [];
if (isArray(files)) return files;
return Object.values(files);
};
const filePaths = getFiles(request.files);
for (const file of filePaths) {
fs.unlink(file.path, (err) => {
if (err) {
console.error(err);
return err;
}
});
}
response.status(status).json(exception.getResponse());
}
}
I have an Express server running on Lambda. There is a custom lambda authorizer attached to his lambda which passes auth headers via events.
My goal is to extract these request context values and attach them to the event headers of the lambda and then pass it on to the Express request headers which can then be used across my route implementations
lambda.ts
'use strict';
import { APIGatewayProxyHandler } from "aws-lambda";
import serverlessExpress from "#vendia/serverless-express";
import app from "./express.app";
import MongoDbConnectionService from "./services/mongodb-connection-service";
let connection = null;
let serverlessExpressInstance = null;
export const handler: APIGatewayProxyHandler = async (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
const authHeaders = (event.requestContext || {}).authorizer || {};
if(connection == null) {
connection = await MongoDbConnectionService.connect();
}
if(serverlessExpressInstance) {
return serverlessExpressInstance(event, context, callback);
}
['principalId'].forEach((headerKey) => {
if (authHeaders.hasOwnProperty(headerKey)) {
event.headers[headerKey.toLowerCase()] = authHeaders[headerKey];
}
});
console.log("Event headers: ", event.headers);
serverlessExpressInstance = serverlessExpress({ app });
return serverlessExpressInstance(event, context, callback);
};
As you can see that I am extracting "principalId" from the event.requestContext and adding it to the event.headers.
When I log the event.headers it does show that "principalid" is included in it.
express server
'use strict';
import cors from 'cors';
import express from 'express';
import 'reflect-metadata';
import { ExpressRouter } from './routes/routes';
import path from 'path';
class ExpressApplication {
public app: express.Express;
private readonly router: express.Router;
constructor() {
const expressRouter = new ExpressRouter();
this.app = express();
this.router = express.Router();
this.app.use(cors());
this.app.use(express.json());
this.app.use(express.urlencoded({ extended: true }));
expressRouter.setRoutes(this.app, this.router);
}
}
const app = new ExpressApplication();
export default app.app;
route
app.post("/calendar-integration", requestValidator(CalIntegrationValidationModel), (req: Request, res: Response) => {
console.log(req.headers);
res.send("Testing this shit");
});
In the above console.log(req.headers) the principalid header is missing. I am unable to figure out what I might be doing wrong, this has been working for me when I was using aws-serverless-express before it got depreciated in favor of #vendia/serverless-express.
Seems like we need to pass the custom headers to event headers via multiValueHeaders.
So the code should be
['principalId'].forEach((headerKey) => {
if (authHeaders.hasOwnProperty(headerKey)) {
event.multiValueHeaders[headerKey.toLowerCase()] = authHeaders[headerKey];
}
});
I'm using Express into a TypeScript project and I have the following situation
This is my route file
...
import findAllUsersFactory from "src/factory/FindAllUsers";
routes.get("/users", findAllUsersFactory().handle);
...
This is the factory where I do a sequence of injections
const findAllUsersFactory = () => {
const findAllUserRepository = new PrismaUsersRepository();
const findAllUsersBusiness = new FindAllUsersBusiness(findAllUserRepository);
const findAllUsersController = new FindAllUsersController(findAllUsersBusiness);
return findAllUsersController;
};
This is my Controller
class FindAllUsersController {
constructor(private findUserBusiness: FindAllUsersBusiness) { }
async handle(request: Request, response: Response) {
const allUsers = await this.findUserBusiness.execute();
return response.status(200).send({ allUsers });
}
}
And finally my Business
class FindAllUsersBusiness {
constructor(private usersRepository: IUsersRepository) {}
async execute() {
return this.usersRepository.findAll();
}
}
The problem is that I'm getting an error "Cannot read property 'execute' of undefined" because the findUserBusiness into handle function is undefined. And what I can't understand is that if I change my route to
routes.get("/users", (request, response) => {
findAllUsersFactory().handle(request, response);
});
it works
I've tried to log the functions, but I can say why findUserBusiness is undefined since it came from the constructor, and since the handle functions came from an instance of FindAllUsersController it should have it "defined"
You need to make some adjustments in order to adapt your factory to the way router.get expects its parameters.
const findAllUsersFactory = (req, res) => {
const findAllUserRepository = new PrismaUsersRepository();
const findAllUsersBusiness = new FindAllUsersBusiness(findAllUserRepository);
const findAllUsersController = new FindAllUsersController(findAllUsersBusiness);
return findAllUsersController.handle(req, res)
};
Then in your router you need to do the following:
routes.get("/users", findAllUsersFactory);
I have an API (Localhost:3000) using node and a front end (Localhost:4200) using Angular 6. Using my regular chrome browser, I am able to CRUD to the database in the API but when I use the android emulator using (10.0.2.2:4200), I cannot do any of the CRUD to the database anymore. Please see my codes below:
Node [index.js]
const express = require("express");
const nedb = require("nedb");
const rest = require("express-nedb-rest");
const cors = require("cors");
const app = express();
const datastore = new nedb({
filename: "mycoffeeapp.db",
autoload: true
});
const restAPI = rest();
restAPI.addDatastore('coffees', datastore);
app.use(cors());
app.use('/', restAPI);
app.listen(3000);
angular front end
This is in the data.service
import { Injectable } from "#angular/core";
import { HttpClient } from "#angular/common/http";
#Injectable({
providedIn: "root"
})
export class DataService {
public endpoint = "http://localhost:3000";
constructor(
private http: HttpClient
) {}
getList(callback) {
this.http.get(`${this.endpoint}/coffees`)
.subscribe(response => {
callback(response);
});
}
get(coffeeId: string, callback) {
this.http.get(`${this.endpoint}/coffees/${coffeeId}`)
.subscribe(response => {
callback(response);
});
}
save(coffee, callback) {
if (coffee._id) {
this.http.put(`${this.endpoint}/coffees/${coffee._id}`, coffee)
.subscribe(response => {
callback(true);
});
} else {
this.http.post(`${this.endpoint}/coffees`, coffee)
.subscribe(response => {
callback(true);
});
}
}
}
in the component:
constructor(
private data: DataService,
private router: Router,
private gls: GeoLocationService
) { }
ngOnInit() {
this.data.getList(list => {
this.list = list;
});
}
If you run an emulated android device and try to access your development environment environment on 10.0.2.2:4200, you'll be able to reach the angular app provided that th emulator is on the same network.
Now, you need to make sure that your API is reachable from outside of your local machine, and, in your angular front, set the API url using an IP address
export class DataService {
public endpoint = "http://10.0.2.2:3000";
If you use localhost, this will point to the emulated device itself, which does not have you API runnnig
I'm new in typescript. In my node-express app, I want to call public function. But this is always undefined, so when I call public function, it throw always error. My code is given below:
app.ts
import * as express from 'express';
import User from './user/ctrl';
class App {
public express: express.Application;
constructor() {
this.express = express();
this.routes();
}
private routes():void {
let router = express.Router();
router.get('/', User.index);
this.express.use('/', router);
}
}
export default new App().express;
./user/ctrl.ts
class User {
public demo:string;
constructor() {
this.demo = "this is text";
}
public infox() {
console.log("demoo test : ", this.demo);
}
public index(req:any, res:any) {
console.log(this) // output: undefined
this.infox(); // throw an error.
}
}
const user = new User();
export default user;
Server run at port 3000.
Any suggestion??
When you passed a reference the User.index function, the this inside it will change based on how it is called. Or when strict mode is on this will be undefined.
Change router.get('/', User.index); to router.get('/', (req, res) => User.index(req, res));. Notice that User.index is wrapped inside an arrow function which captures the correct this when User.index is called.
See red flags for this in TypeScript