NestJS: present response content from URL on healthcheck - nestjs

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 { }

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.

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.

NestJS: dynamically call various services for batch processing

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.

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.'
}
}

How to get GET paramater in Angular2?

By accessing myproject.dev/people?filter%5Bindustry%5D=finance&filter%5BstartWith%5D=a, Angular2 point the url to myproject.dev/people
Here is my RouteConfig:
#RouteConfig([
{
path: '/people',
name: config.route.main,
component: MainComponent,
useAsDefault: true
}
])
In MainComponent:
/// <reference path="../../../typings/angular2.d.ts" />
import {Component, Injector} from 'angular2/core';
import {ROUTER_DIRECTIVES, Router, RouteParams} from 'angular2/router';
import {BaseResourceComponent} from '../../Component/BaseResourceComponent';
import {Status as MainStatus} from '../../reusable/modules/status.svc';
import {Status} from '../../reusable/modules/status.svc';
import {Config} from "./Config";
import URI from 'urijs';
export class MainComponent extends BaseResourceComponent {
constructor(config: Config, status: Status, mainStatus: MainStatus, private router: Router, private routeParams: RouteParams) {
super(config, status, mainStatus);
}
onInit() {
var path = new URI(window.location.href);
path.setQuery('filter[industry]', 'fashion');
path.setQuery('filter[startWith]', 'a');
console.log(path);
console.log(this.router);
//this.router.root.lastNavigationAttempt = "/people?filter%5Bindustry%5D=finance&filter%5BstartWith%5D=a"
console.log(this.routeParams);
// this.routeParams returns {params: Object}
// this.routeParams.params.get('filter') return null
}
}
I still can get it from this.router.root.lastNavigationAttempt, but this is kind of tricky way to get it only. Any better way to get the GET parameters?
In the root component you can inject the router and subscribe, then on route events get the params from the router like
export class AppComponent {
constructor(private router:Router) {
router.subscribe(route => {
console.debug(this.router.currentInstruction.component.params);
});
}
}
On components added by the router you can inject RouteParams directly like
export class Other{
constructor(private routeParams: RouteParams) {
console.debug(this.routeParams);
console.log(this.routeParams.get('filter_industry'));
console.log(this.routeParams.get('filter_start_with'));
}
}
Plunker example
My solution : certainly not the best way t odo it but it work :
I assume that you have this kin of url :
http://localhost:8080/contextPath/index.html?login=true#token_type=Bearer&expires_in=9999&access_token=xxxXXXXXxxx
//get base url to get the token
if
(this.location == "")
{
console.log("traitement location");
this.location = location.href;
}
//extract all :
if (this.location != "") {
console.log("traitement token");
this.login = this.location.split("?")[0].split("=")[1];
this.token_type = this.location.split("?")[1].split("#")[1].split("&")[0].split("=")[1];
this.expire_in = +this.location.split("?")[1].split("#")[1].split("&")[1].split("=")[1];
this.setLocalDateValid((this.expire_in + this.nowDate()).toString());
this.token = this.location.split("?")[1].split("#")[1].split("&")[2].split("=")[1];
}
// then store it
this.setLocalToken(this.token);
Certainly not the best way to do it but it work perfectly well :)
#Günter Zöchbauer is correct. Child route can only use matrix parameter but not query parameter.

Resources