NestJS: dynamically call various services for batch processing - nestjs

I have some Service classes as follows:
//Cat Service:
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository, getManager } from 'typeorm';
import { CatRepo } from '../tables/catrepo.entity';
import { CatInterface } from './cat.interface';
#Injectable()
export class CatService {
constructor(
#InjectRepository(CatRepo)
private catRepo: Repository<CatRepo>,
) {}
async customFindAll(offset:number, limit: number): Promise<CatRepo[]> {
const entityManager = getManager();
const catRows = await entityManager.query(
`
SELECT * FROM CATREPO
${offset ? ` OFFSET ${offset} ROWS ` : ''}
${limit ? `FETCH NEXT ${limit} ROWS ONLY` : ''}
`,
);
return catRows;
}
formResponse(cats: CatRepo[]): CatInterface[] {
const catsResults: CatInterface[] = [];
.
//form cat response etc.
.
//then return
return catsResults;
}
}
//Pet Service:
import { Injectable } from '#nestjs/common';
import { getManager } from 'typeorm';
import { PetInterface } from './pet.interface';
#Injectable()
export class PetService {
async customFindAll(offset:number, limit: number) {
const entityManager = getManager();
const petRows = await entityManager.query(
`
JOIN ON TABLES......
${offset ? ` OFFSET ${offset} ROWS ` : ''}
${limit ? `FETCH NEXT ${limit} ROWS ONLY` : ''}
`,
);
//returns list of objects
return petRows;
}
formResponse(pets): PetInteface[] {
const petsResults: PetInteface[] = [];
.
. //form pet response etc.
.
//then return
return petsResults;
}
}
I am running a cron BatchService that uses these two services subsequently saving the data into respective batch files.
I'm calling CatService and PetService from the BatchService as follows:
/Start the Batch job for Cats.
if(resource === "Cat") {
//Call Cat Service
result = await this.catService.findAllWithOffest(startFrom, fetchRows);
finalResult = this.catService.formResponse(result);
}
//Start the batch job for Pets.
if(resource === "Pet") {
//Call Pet Service
result = await this.petService.findAllWithOffest(startFrom, fetchRows);
finalResult = this.petService.formResponse(result);
}
However, instead of the above I want to use these Services dynamically.
In order to achieve the CatService and PetService now extends AbstractService...
export abstract class AbstractService {
public batchForResource(startFrom, fetchRows) {}
}
//The new CatService is as follows:
export class CatService extends AbstractService{
constructor(
#InjectRepository(CatRepo)
private catRepo: Repository<CatRepo>,
) {}
.
.
.
}
//the new PetService is:
export class PetService extends AbstractService{
constructor(
) {super()}
.
.
.
}
//the BatchService...
public getService(context: string) : AbstractService {
switch(context) {
case 'Cat': return new CatService();
case 'Pet': return new PetService();
default: throw new Error(`No service found for: "${context}"`);
}
}
However in the CatService I'm getting the a compilation error...(Expected 1 Argument but got 0). What should be the argument passed in the CatService.
Also, the larger question is if this can be achieved by using NestJS useValue/useFactory...If so how to do it?

You can probably use useFactory to dynamically retrieve your dependencies but there are some gotcha's.
You must make the lifecycle of your services transient, since NestJS dependencies are registered as singletons by default. If not, you would get the same first service injected each time, regardless of the context of subsequent calls.
Your context must come from another injected dependency - ExecutionContext, Request or something similarly dynamic, or something you register yourself.
Alternative
As an alternative, you can implement the "servicelocator/factory" pattern. You're already halfway there with your BatchService. Instead of your service creating instances of the CatService and PetService, you have it injected and just return the injected services depending on the context. Like so:
#Injectable()
export class BatchService {
constructor(
private readonly catService: CatService,
private readonly petService: PetService
)
public getService(context: string) : AbstractService {
switch(context) {
case 'Cat': return this.catService;
case 'Pet': return this.petService;
default: throw new Error(`No service found for: "${context}"`);
}
}
}
The alternative is more flexible than using useFactory, since your context is not limited to what is available in the DI container. On the negative side, it does expose some (usually unwanted) infrastructure details to the calling code, but that's the tradeoff you'll have to make.

Related

NestJS lifecycle methods invoked without implementing their interface

I am having a small question about NestJS. In my code, there is a service which looks something like:
`
import { Inject, Injectable } from '#nestjs/common';
import neo4j, { Driver, int, Result, Transaction } from 'neo4j-driver';
import { Neo4jConfig } from './neo4j-config.interface';
import { NEO4J_CONFIG, NEO4J_DRIVER } from './neo4j.constants';
#Injectable()
export class Neo4jService {
constructor(
#Inject(NEO4J_CONFIG) private readonly config: Neo4jConfig,
#Inject(NEO4J_DRIVER) private readonly driver: Driver,
) {}
onApplicationBootstrap() {
console.log('Hello');
}
getDriver(): Driver {
return this.driver;
}
getConfig(): Neo4jConfig {
return this.config;
}
int(value: number) {
return int(value);
}
beginTransaction(database?: string): Transaction {
const session = this.getWriteSession(database);
return session.beginTransaction();
}
getReadSession(database?: string) {
return this.driver.session({
database: database || this.config.database,
defaultAccessMode: neo4j.session.READ,
});
}
getWriteSession(database?: string) {
return this.driver.session({
database: database || this.config.database,
defaultAccessMode: neo4j.session.WRITE,
});
}
read(
cypher: string,
params?: Record<string, unknown>,
databaseOrTransaction?: string | Transaction,
): Result {
if (databaseOrTransaction instanceof Transaction) {
return (<Transaction>databaseOrTransaction).run(cypher, params);
}
const session = this.getReadSession(<string>databaseOrTransaction);
return session.run(cypher, params);
}
write(
cypher: string,
params?: Record<string, unknown>,
databaseOrTransaction?: string | Transaction,
): Result {
if (databaseOrTransaction instanceof Transaction) {
return (<Transaction>databaseOrTransaction).run(cypher, params);
}
const session = this.getWriteSession(<string>databaseOrTransaction);
return session.run(cypher, params);
}
private onApplicationShutdown() {
console.log('Goodbye')
return this.driver.close();
}
}
`
Then in my main.ts file I have this method called:
`
await app.listen(port);
`
As you can see my service does not implement neither onApplicationBootstrap nor onApplicationShutdown.
How does it come that those methods still get invoked? Should I implement onApplicationBootstrap and onApplicationShutdown or not?
As you can also see I' d like that my onApplicationBootstrap is a private method which would not be possible if I implement the interface.
So, I would like to ask you:
Why the two lifecycle methods get called event without implementing the interface?
Should I implement those interfaces at all or just go on and use the methods which would allow me to define them as private?
I expected those methods to not work without implementing the interfaces
The Typescript interface is there to help us as devs. It doesn't exist at runtime, there's no information about it, so the only thing Nest can do is just check "Hey, does this class have the onModuleInit method?" If yes, add it to a list of classes to call onModuleInit. Do the same with the other lifecycle methods.
The interfaces aren't explicitly necessary, but they do give us devs a better idea of the class by just looking at the export class... line because we can see what is implemented/extended.

NestJs - Validate request body using class-validator having 2 options for body class

I have a rest call, which might receive body of type classA or classB.
I need to keep it as 2 different classes.
Example -
// classes -
class ClassA {
#IsString()
#Length(1, 128)
public readonly name: string;
#IsString()
#Length(1, 128)
public readonly address: string;
}
class ClassB {
#IsString()
#Length(1, 10)
public readonly id: string;
}
// my request controller -
#Post('/somecall')
public async doSomething(
#Body(new ValidationPipe({transform: true})) bodyDto: (ClassA | ClassB) // < not validating any of them..
): Promise<any> {
// do something
}
The issue is, that when having more than one class, body is not validated.
How can I use 2 or more classes and validate them using class-validator?
I don't want to use same class..
Thank you all :)
I don't want to use same class..
Then it won't be possible, at least not with Nest's built-in ValidationPipe. Typescript doesn't reflect unions, intersections, or other kinds of generic types, so there's no returned metadata for this parameter, and if there's no metadata that's actionable Nest will end up skipping the pipe.
You could probably create a custom pipe to do the validation for you, and if you have two types you're probably going to have to. You can still call the appropriate class-transformer and class-validator methods inside of the class too.
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '#nestjs/common';
import { of } from 'rxjs';
#Injectable()
export class CheckTypeInterceptor implements NestInterceptor {
constructor() {}
async intercept(context: ExecutionContext, next: CallHandler) /*: Observable<any>*/ {
const httpContext = context.switchToHttp();
const req = httpContext.getRequest();
const bodyDto = req.body.bodyDto;
// Need Update below logic
if (bodyDto instanceof ClassA || bodyDto instanceof ClassB) {
return next.handle();
}
// Return empty set
return of([]);
}
}
#UseInterceptors(CheckTypeInterceptor)
export class ApiController {
...
}
Encountered a similar situation where I had to validate some union type request. The solution I ended up with was a custom pipe as Jay McDoniel suggested here. The logic would vary depending on the request body you are dealing with, but per the question in case the following may work
Custom pipe:
import { ArgumentMetadata, BadRequestException, Inject, Scope } from "#nestjs/common";
import { PipeTransform } from "#nestjs/common";
import { plainToInstance } from "class-transformer";
import { validate } from "class-validator";
import { ClassADto } from '../repository/data-objects/class-a.dto';
import { ClassBDto } from '../repository/data-objects/class-b.dto';
export class CustomPipeName implements PipeTransform<any> {
async transform(value: any, { metatype, type }: ArgumentMetadata): Promise<any> {
if (type === 'body') {
const classA = plainToInstance(ClassADto, value);
const classB = plainToInstance(ClassBDto, value);
const classAValidationErrors = await validate(classA);
const classBValidationErrors = await validate(classB);
if (classAValidationErrors.length > 0 && classBValidationErrors.length > 0) {
throw new BadRequestException('some fancy info text');
}
}
return value;
}
}
Controller usage:
#Post('/somecall')
public async doSomething(
#Body(new CustomePipeName()) bodyDto: (ClassA | ClassB)
): Promise<any> {
// do something
}

NestJS: present response content from URL on healthcheck

I'm trying to develop a healthcheck endpoint with NestJS (in which I have no experience). One of the dependencies I want to check is Twilio's SMS service. So far, the best URL I've found to gather this information is https://status.twilio.com/api/v2/status.json. The problem here is that I don't want to merely ping this address, but to gather it's JSON response and present some of the information it provides, namely these:
Is it possible, using (or not) the Terminus module? In the official docs I didn't find anything regarding this, only simpler examples using pingCheck / responseCheck: https://docs.nestjs.com/recipes/terminus
Yes, it is possible.
I have never used this, but HttpHealthIndicator has responseCheck method to check depends on the API response message. You can specify a callback function to analyze responses from the API. The callback function should return boolean represents the status of the API.
I couldn't find this in the documents, but you can see it here.
Although meanwhile the logic for this healthcheck has changed (and so this question became obsolete), this was the temporary solution I've found, before it happened (basically a regular endpoint using axios, as pointed out in one of the comments above):
Controller
import { Controller, Get } from '#nestjs/common';
import { TwilioStatusService } from './twilio-status.service';
#Controller('status')
export class TwilioStatusController {
constructor(private readonly twilioStatusService: TwilioStatusService) {}
#Get('twilio')
getTwilioStatus() {
const res = this.twilioStatusService.getTwilioStatus();
return res;
}
}
Service
import { HttpService } from '#nestjs/axios';
import { Injectable } from '#nestjs/common';
import { map } from 'rxjs/operators';
#Injectable()
export class TwilioStatusService {
constructor(private httpService: HttpService) {}
getTwilioStatus() {
return this.httpService
.get('https://status.twilio.com/api/v2/status.json')
.pipe(map((response) => response.data.status));
}
}
Of course this wasn't an optimal solution, since I had to do this endpoint + a separated one for checking MongoDB's availability (a regular NestJS healthcheck, using Terminus), the goal being an healthcheck that glued both endpoints together.
It is possible to merge in any property to the resulting object. You can see that in the TypeScript Interface
/**
* The result object of a health indicator
* #publicApi
*/
export declare type HealthIndicatorResult = {
/**
* The key of the health indicator which should be uniqe
*/
[key: string]: {
/**
* The status if the given health indicator was successful or not
*/
status: HealthIndicatorStatus;
/**
* Optional settings of the health indicator result
*/
[optionalKeys: string]: any;
};
};
And here is an example:
diagnostics/health/healthcheck.controller
import { Controller, Get } from '#nestjs/common'
import { ApiTags } from '#nestjs/swagger'
import { HttpService } from '#nestjs/axios'
import { HealthCheckService, HealthCheck, HealthIndicatorStatus, HealthCheckError } from '#nestjs/terminus'
#ApiTags('diagnostics')
#Controller('diagnostics/health')
export class HealthController {
constructor(
private health: HealthCheckService,
private httpService: HttpService,
) { }
#Get()
#HealthCheck()
check() {
return this.health.check([
() => this.httpService.get('http://localhost:9002/api/v1/diagnostics/health').toPromise().then(({ statusText, config: { url }, data }) => {
const status: HealthIndicatorStatus = statusText === 'OK' ? 'up' : 'down'
return { 'other-service': { status, url, data } }
}).catch(({ code, config: { url } }) => {
throw new HealthCheckError('Other service check failed', { 'other-service': { status: 'down', code, url } })
}),
])
}
}
diagnostics/diagnostics.module.ts
import { Module } from '#nestjs/common'
import { TerminusModule } from '#nestjs/terminus'
import { HttpModule } from '#nestjs/axios'
import { HealthController } from './health/health.controller'
#Module({
imports: [
HttpModule,
TerminusModule,
],
controllers: [HealthController],
})
export class DiagnosticsModule { }

I'd like to DI for repository interface and service interface like Spring using typedi

I'd like to DI for repository interface and service interface like Spring using typedi.
Below code (example code of DI for repository) is working correctly when calling api.
Repository
import { Service } from "typedi";
import { EntityRepository, Repository } from "typeorm";
import { User } from "../entity/User";
export interface IUserRepository {
findAllUsers();
findUserByUserId(id: number);
addUser(user: any);
removeUserByUserId(user: any);
}
#Service()
#EntityRepository(User)
export class UserRepository
extends Repository<User>
implements IUserRepository {
findAllUsers() {
return this.find();
}
findUserByUserId(id: number) {
return this.findOne({ id });
}
addUser(user: any) {
return this.save(user);
}
removeUserByUserId(user: any) {
return this.remove(user);
}
}
Service
import { Service } from "typedi";
import { InjectRepository } from "typeorm-typedi-extensions";
import { User } from "../entity/User";
import { UserRepository } from "../repository/userRepository";
export interface IUserService {
all();
one(id: any);
save(user: any);
remove(id: any);
}
#Service()
export class UserService implements IUserService {
#InjectRepository(User)
private userRepository: UserRepository;
async all() {
return this.userRepository.findAllUsers();
}
async one(id: any) {
let user = await this.userRepository.findUserByUserId(id);
if (typeof user === "undefined") {
throw new Error(`userId ${id} is not found.`);
}
return user;
}
async save(user: any) {
return this.userRepository.addUser(user);
}
async remove(id: any) {
let userToRemove = await this.userRepository.findUserByUserId(id);
if (typeof userToRemove === "undefined") {
throw new Error(`userId ${id} is not found.`);
}
return this.userRepository.removeUserByUserId(userToRemove);
}
}
However, when I'd like to inject repository using interface, it does not work correctly and occur the error message.
The build is succes. The error message is occur when calling api
In addition, error message are different for the first time and the second time later when call api.
like this
Repository
import { Service } from "typedi";
import { InjectRepository } from "typeorm-typedi-extensions";
import { User } from "../entity/User";
import { UserRepository } from "../repository/userRepository";
...
#Service()
export class UserService implements IUserService {
#InjectRepository(User)
private userRepository: UserRepository;
async all() {
return this.userRepository.findAllUsers();
}
...
}
Error message of first time.
{
"name": "CustomRepositoryNotFoundError",
"message": "Custom repository Object was not found. Did you forgot to put #EntityRepository decorator on it?",
"stack": "CustomRepositoryNotFoundError: Custom repository Object was not found. Did you forgot to put #EntityRepository decorator on it? (The following is omitted)"
}
Error message of second time later.
{
"name": "TypeError",
"message": "Cannot read property 'all' of undefined",
"stack": "TypeError: Cannot read property 'all' of undefined(The following is omitted)"
}
Service does not work well either.
Below code is success code.
Controller
import {
Get,
JsonController,
OnUndefined,
Param,
Post,
Body,
Delete,
} from "routing-controllers";
import { Inject, Service } from "typedi";
import { UserService } from "../service/userService";
#Service()
#JsonController("/users")
export class UserRestController {
#Inject()
private userService: UserService;
#Get("/")
getAll() {
return this.userService.all();
}
#Get("/:id")
#OnUndefined(404)
getOne(#Param("id") id: number) {
return this.userService.one(id);
}
#Post("/")
add(#Body() user: any) {
return this.userService.save(user);
}
#Delete("/:id")
delete(#Param("id") id: number) {
return this.userService.remove(id);
}
}
But the below is not work well.
In this case, even the build does not work.
Controller
import {
Get,
JsonController,
OnUndefined,
Param,
Post,
Body,
Delete,
} from "routing-controllers";
import { Inject, Service } from "typedi";
import { IUserService } from "../service/userService";
#Service()
#JsonController("/users")
export class UserRestController {
#Inject()
private userService: IUserService;
#Get("/")
getAll() {
return this.userService.all();
}
#Get("/:id")
#OnUndefined(404)
getOne(#Param("id") id: number) {
return this.userService.one(id);
}
#Post("/")
add(#Body() user: any) {
return this.userService.save(user);
}
#Delete("/:id")
delete(#Param("id") id: number) {
return this.userService.remove(id);
}
}
Error Message
CannotInjectValueError: Cannot inject value into "UserRestController.userService". Please make sure you setup reflect-metadata properly and you don't use interfaces without service tokens as injection value.
As described at the beginning, I'd like to DI for repository interface and service interface like Spring using typedi.
TypeDI cannnot using like this?
or my code is wrong?
Please help me.
Thank you.
Interfaces are ephemeral, they don't actually exist when your code is running doing its job, they exist only when you write the code. Classes, on the other hand, are pretty much tangible, they always exist. That's why when you use UserService class, it works, but when you use IUserService interface, it doesn't work.
The error you are getting tells you something useful:
Please make sure […] you don't use interfaces without service tokens as injection value.

In nestjs, is it possible to specify multiple handlers for the same route?

Is it possible to specify multiple handler for the same route?
Any HTTP GET request to the /test route should call the get handler unless the query string watch === '1', in which case it should call the watch handler instead.
import { Controller, Get } from '#nestjs/common';
#Controller('test')
export class TestController {
#Get()
get(){
return 'get'
}
#Get('?watch=1')
watch(){
return 'get with watch param'
}
}
As the framework does not seem to support this, I was hoping to be able to write a decorator to abstract this logic.
ie.
import { Controller, Get } from '#nestjs/common';
import { Watch } from './watch.decorator';
#Controller('test')
export class TestController {
#Get()
get(){
return 'get'
}
#Watch()
watch(){
return 'get with watch param'
}
}
Can this be done? Can anyone point me to the right direction?
I would try to keep the complexity low and simply implement two functions.
#Controller('test')
export class TestController {
myService: MyService = new MyService();
#Get()
get(#Query('watch') watch: number) {
if (watch) {
return myService.doSomethingB(watch);
} else {
return myService.doSomethingA();
}
}
}
export class MyService {
doSomethingA(): string {
return 'Do not watch me.'
}
doSomethingB(watch: number): string {
return 'Watch me for ' + watch + ' seconds.'
}
}

Resources