In NestJS, param annotation is not working with controller - path

I am working on a simple project using NestJS.
I came here to ask for help because there was a problem while I was working on the project separating the controller and the service.
I am going to get the path value of the Get method from the controller and hand it over to the service.
In this process, the controller was set up as follows.
import { Controller, Get, Param, Post, Query } from '#nestjs/common';
import { AppService } from 'src/app.service.ts'
#Controller('app')
export class AppController {
constructor(private readonly appService: AppService) {}
#Get(':vendor/art/:artId')
findOneByVenderAndUid(
#Param('vender') vender: string,
#Param('artId') artId: string,
) {
return this.appService.findOneByVenderAndUid(vender, artId);
}
}
In addition, the global pipeline was set in main.ts as follows.
import { ValidationPipe } from '#nestjs/common';
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transformOptions: {
enableImplicitConversion: true,
},
}),
);
await app.listen(3000);
}
bootstrap();
However, when I output the path value received from the service to the console, it appeared as undefined and could not be used.
Is there anything wrong with the part that I implemented?

Typo in the #Param(). The string passed to the annotation must mat ch the string used in the url. In this case :vendor does not match #Param('vender')

Related

Nestjs - There is no matching event handler defined in the remote service

I'm trying to handle the message published on topic test_ack from online MQTT broker using microservices. But I'm getting the error.
There is no matching event handler defined in the remote service.
My Code:
main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { Transport } from '#nestjs/common/enums/transport.enum';
var url = 'mqtt://test.mosquitto.org';
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.MQTT,
options: {
url: url
}
});
await app.listenAsync();
}
bootstrap();
app.controller.ts
import { Controller } from '#nestjs/common';
import { MessagePattern } from '#nestjs/microservices';
#Controller()
export class AppController {
constructor() {}
#MessagePattern('test')
ackMessageTestData(data:unknown) {
console.log(data.toString());
return 'Message Received';
}
}
As I don't have edit permission, I will post it as a new answer. As mentioned in the above answer. We have to use #EventPattern('test_ack').
The published message should be in format {data: 'Your message'} and should be serialized before publishing as mentioned here.
client.publish('test_ack', JSON.stringify({data: 'test data'})).

Angular MatSnackBar not working from custom class

I am trying to create a toast using Material Snackbar from a custom class.
I am getting an error in my custom class ( Unable to find open from undefined. ) but working fine in user.service.ts
If ngZone is used, then i am getting an error, (unable to find run from undefined)
Note: In ErrorHandler Class
console.log(this.snackBar) // gives undefined
app.module.ts
providers: [ErrorHandler],
bootstrap: [AppComponent]
})
export class AppModule { }
User Service
import { Injectable } from '#angular/core';
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
import { catchError } from 'rxjs/operators';
import { environment } from '../../environments/environment';
import { ErrorHandler } from '../classes/error-handler';
import {MatSnackBar} from '#angular/material';
#Injectable({
providedIn: 'root'
})
export class UserService {
private url = environment.api+'/login';
constructor(private http: HttpClient, private eh:ErrorHandler, private sb: MatSnackBar) { }
login(credentials){
this.sb.open("hello world"); // Working Fine
return this.http.post(this.url, {})
.pipe(
catchError(this.eh.handleError)
);
}
}
Error Handler Class
import {Component, Injectable, NgZone} from '#angular/core';
import { HttpErrorResponse } from '#angular/common/http';
import { throwError } from 'rxjs';
import * as _ from 'lodash';
import {MatSnackBar} from '#angular/material';
#Injectable({
providedIn: 'root'
})
export class ErrorHandler {
constructor (private snackBar: MatSnackBar) {}
public handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
console.error('An error occurred:', error.error.message);
} else {
console.error("Error code working")
console.log(this.snackBar) // gives undefined
this.snackBar.open("Hello world"); // Throwing Error
}
// return an observable with a user-facing error message
return throwError('Something bad happened; please try again later.');
};
}
Thanks to all. I fixed it. Unfortunately i missed the stackoverflow link.
Change in
catchError((res) => this.eh.handleError(res))
did the trick
UserService
import { Injectable } from '#angular/core';
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
import { catchError } from 'rxjs/operators';
import { environment } from '../../environments/environment';
import { ErrorHandler } from '../classes/error-handler';
import {MatSnackBar} from '#angular/material';
#Injectable({
providedIn: 'root'
})
export class UserService {
private url = environment.api+'/login';
constructor(private http: HttpClient, private eh:ErrorHandler, private sb: MatSnackBar) { }
login(credentials){
this.sb.open("hello world"); // Working Fine
return this.http.post(this.url, {})
.pipe(
catchError((res) => this.eh.handleError(res)) // change in this line
);
}
}
I stumbled upon the same issue in my application a few days ago. The reason is the context of this.
In
This.sb.open("hello world"); // Working Fine
the context of this is the UserService class. While in
this.snackBar.open("Hello world"); // Throwing Error
the context of this changed. Probably to CatchSubscriber.
You already mentioned that:
catchError((res) => this.eh.handleError(res)) // change in this line
resolves the error. Because it changed the context of this back to the UserService class. An alternative solution is to use the arrow syntax for the whole login():
login = (credentials) => {
// your code goes here
}

How to integrate Neo4j database, NestJS framework and GraphQL?

I'm trying to integrate my REST API (NestJS) with new Neo4j database with GraphQL queries. Anybody succeed? Thanks in advance
EDIT 1: (I added my code)
import { Resolver } from "#nestjs/graphql";
import { Query, forwardRef, Inject, Logger } from "#nestjs/common";
import { Neo4jService } from "src/shared/neo4j/neoj4.service";
import { GraphModelService } from "./models/model.service";
import { Movie } from "src/graphql.schema";
#Resolver('Movie')
export class GraphService {
constructor(private readonly _neo4jService: Neo4jService) {}
#Query()
async getMovie() {
console.log("hello");
return neo4jgraphql(/*i don't know how get the query and params*/);
}
}
I am using a NestInterceptor to accomplish this:
#Injectable()
export class Neo4JGraphQLInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
next: CallHandler<any>,
): Observable<any> | Promise<Observable<any>> {
const ctx = GqlExecutionContext.create(context);
return neo4jgraphql(
ctx.getRoot(),
ctx.getArgs(),
ctx.getContext(),
ctx.getInfo(),
);
}
}
To use it in your Resolver:
#Resolver('Movie')
#UseInterceptors(Neo4JGraphQLInterceptor)
export class MovieResolver {}
My GraphQLModule is configured like this:
#Module({
imports: [
GraphQLModule.forRoot({
typePaths: ['./**/*.gql'],
transformSchema: augmentSchema,
context: {
driver: neo4j.driver(
'bolt://neo:7687',
neo4j.auth.basic('neo4j', 'password1234'),
),
},
}),
],
controllers: [...],
providers: [..., MovieResolver, Neo4JGraphQLInterceptor],
})
Note the usage of transformSchema: augmentSchema to enable auto-generated mutations and queries (GRANDStack: Schema Augmentation)
Hope that helps a bit!
This is what works for me...not as elegant as I would like but it works; I want to have only one service/provider accessing my db not the service from each module even though that works also. So I am sticking with the Nest format of myModule->myResolver->myService-->Neo4jService. So Neo4jService is injected in all xService(s). I am using neo4jGraphql and augmentSchema and Cypher when necessary.
Code:
**appmodule.ts**
....
import { makeExecutableSchema } from 'graphql-tools';
import { v1 as neo4j } from 'neo4j-driver';
import { augmentTypeDefs, augmentSchema } from 'neo4j-graphql-js';
import { Neo4jService } from './neo4j/neo4j.service';
import { MyModule } from './my/my.module';
import { MyResolver } from './my/my.resolver';
import { MyService } from './my/my.service';
....
import { typeDefs } from './generate-schema'; // SDL type file
...
const driver = neo4j.driver('bolt://localhost:3000', neo4j.auth.basic('neo4j', 'neo4j'))
const schema = makeExecutableSchema({
typeDefs: augmentTypeDefs(typeDefs),
});
const augmentedSchema = augmentSchema(schema); // Now we have an augmented schema
#Module({
imports: [
MyModule,
GraphQLModule.forRoot({
schema: augmentedSchema,
context: {
driver,
},
}),
],
controllers: [],
providers: [ Neo4jService,
myResolver,
],
})
export class AppModule {}
**myResolver.ts**
import { Args, Mutation, Query, Resolver } from '#nestjs/graphql';
import { MyService } from './my.service';
#Resolver('My')
export class MyResolver {
constructor(
private readonly myService: MyService) {}
#Query()
async getData(object, params, ctx, resolveInfo) {
return await this.myService.getData(object, params, ctx, resolveInfo);
}
*//Notice I am just passing the graphql params, etc to the myService*
}
**myService.ts**
import { Injectable } from '#nestjs/common';
import { Neo4jService } from '../neo4j/neo4j.service';
#Injectable()
export class MyService {
constructor(private neo4jService: Neo4jService) {}
async getData(object, params, ctx, resolveInfo) {
return await this.neo4jService.getData(object, params, ctx, resolveInfo);
}
*// Again I am just passing the graphql params, etc to the neo4jService*
}
**neo4jService.ts**
import { Injectable } from '#nestjs/common';
import { neo4jgraphql } from 'neo4j-graphql-js';
#Injectable()
export class Neo4jService {
getData(object, params, ctx, resolveInfo) {
return neo4jgraphql(object, params, ctx, resolveInfo);
}
.....
......
}
So basically I postponed using neo4jgraphql until we arrive at neo4jService. Now all my DB calls are here.....as I said not elegant but it works.
Challenges: Graphql generate would not accept #relation...I found out that a change was made and now you need augmentTypeDefs.
...hope this helps
EDIT
Nestjs takes an awful long time to process the augmentSchema...so I would recommend skipping it..for now
Here is an example i created for (NestJS + GraphQL + Neo4j). I hope if this may help!
NestJS + GraphQL + Neo4j
I have not worked on GraphQL, but I know there is an npm package(Neo4j-graphql-js) to translate GraphQL queries into Cypher queries. It makes it easier to use GraphQL and Neo4j together.
Also, check GRANDstack it is a full-stack development integration for building Graph-based applications.
I suggest you to visit Neo4j Community.

How to display data from mvc controller using angular2

I have created a project including angular2 for front-end and i also created webapi project to consume data from database.
Controller Code return model:
UserInfo = JsonConvert.DeserializeObject<List<UsersVM>>(Response);
I want to iterate over this data model in my angular view. i trid creating angular http calls. but this not acceptable in my case. i need to call webapi from my mvc controllers and just to render that data from angular2 views.
Angular Model is :
export interface IUser {
Id: number;
ProductName: string;
ProductPrice: string;
}
Angular Service Code is:
import {
Injectable
} from '#angular/core';
import {
Http, Response, RequestOptions,
Request, RequestMethod, Headers
} from '#angular/http';
import {
IUser
} from './user';
import {
Observable
} from 'rxjs/Observable';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
#Injectable()
export class UserService {
private _productUrl = 'Home/GetAllProducts';
constructor(private _http: Http) { }
getProducts(): Observable<IUser[]> {
return this._http.get(this._productUrl)
.map((response: Response) => <IUser[]>response.json().value)
.catch(this.handleError);
}
private handleError(error: Response) {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
}
Stuck in this, any links available in google doesn't correctly solve my issue.
Please guide.
Thanks
You have mentioned _productUrl as your API path, but it should be actual API URL with domain name and action call.
as :
private _productUrl = 'localhost:50962/products/';
Eg.
Service.ts
import { Injectable } from '#angular/core';
import { Http, Response, RequestOptions,Request, RequestMethod, Headers } from '#angular/http';
import { IUser } from './user';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
#Injectable()
export class UserService {
private _productUrl = 'localhost:50962/products/';
constructor(private _http: Http) { }
getProducts(): Observable<IUser[]> {
let header = this.initHeaders();
let options = new RequestOptions({ headers: header, method: 'get' });
return this._http.get(this._productUrl + 'getAllProducts', options)
.map((response: Response) => <IUser[]>response.json().value)
.catch(this.handleError);
}
private initHeaders(): Headers {
var headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Access-Control-Allow-Origin', '*');
return headers;
}
private handleError(error: Response) {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
}
Also you can set environment variable for your API and use it in all your services. environment.API
export const environment = {
API: 'http://localhost:50962/',
WordPressAPI: 'http://localhost:58451/',
FacebookClientId: '45******',
}
return this._http.get(environment.API + 'products/getAllProducts', options)
.map((response: Response) => <IUser[]>response.json().value)
.catch(this.handleError);
You can use ngFor and the AsyncPipe
<div *ngFor="let user of getProducts() | async">
{{user.Id}}
{{user.ProductName}}
{{user.ProductPrice}}
</div>
A combination from both (use full path) Amol Bhor & Leon, and instead has the uri vars into env file use constants.ts file because for me environment is related to dev or prod environments. And to avoid memory leaks use asyn pipe, if not use unsubscribe into onDestroy method. Check docs for detail info.

Problems with URL handling with Spring Boot and Angular 2

I am trying to develop an application with Spring Boot for the back end and Angular 2 for the front end, using maven.
The angular 2 front end is in the src/main/resources/static dir of the project.
When I enter the http://localhost:8080/ URL in my browser, all is fine: I can access the angular 2 front end, and the front end can communicate with the rest api perfectly. My angular 2 routing works fine: when I click on a link on the front end, I go the right page and the browser url bar shows the right things (ie. http://localhost:8080/documents)
But the problem is when I try to directly write the same URL in the browser. Spring take over the front end and says the is no mapping for /documents.
Is there a way to tell spring boot to only "listen" to /api/* URL and to "redirect" all the others to the front end?
Here is my Spring Controller class:
#RestController
#RequestMapping("/api")
public class MyRestController {
#Autowired
private DocumentsRepository documentRepository;
#CrossOrigin(origins = "*")
#RequestMapping(value = "/documents/list",
method = RequestMethod.GET,
produces = "application/json")
public Iterable<RfDoc> findAllDocuments() {
return documentRepository.findAll();
}
}
Here is the main application class:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Here is my app.route.ts:
import { provideRouter, RouterConfig } from '#angular/router';
import { DocumentComponent } from './doc.component';
const routes: RouterConfig = [
{
path: '',
redirectTo: 'documents',
pathMatch: 'full'
},
{
path: "documents",
component: DocumentComponent
}
];
export const appRouterProviders = [
provideRouter(routes)
];
Ok, so I found a perfectly fine solution (for me, at least): I change my location for the old AngularJS 1.X way, with the # in the URL (i.e. http://localhost:8080/#/documents ).
To obtain this behaviour, I change my bootstrap like this
import { bootstrap } from '#angular/platform-browser-dynamic';
import { HTTP_PROVIDERS } from '#angular/http';
import { AppComponent } from './app.component';
import { appRouterProviders } from './app.routes';
import { AuthService } from './auth.service';
bootstrap(AppComponent, [AuthService,
appRouterProviders,
HTTP_PROVIDERS,
{ provide: LocationStrategy, useClass: HashLocationStrategy }
]);
Hope this can help somebody!

Resources