I've read nestjs microservice and kafka documentation but I couldn't figure out some of it. I'll be so thankful if you can help me out.
So as the docs says I have to create a microService in main.ts file as follows:
const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
transport: Transport.KAFKA,
options: {
client: {
brokers: ['localhost:9092'],
}
}
});
await app.listen(() => console.log('app started'));
Then there is a kafkaModule file like this:
#Module({
imports: [
ClientsModule.register([
{
name: 'HERO_SERVICE',
transport: Transport.KAFKA,
options: {
client: {
clientId: 'hero',
brokers: ['localhost:9092'],
},
consumer: {
groupId: 'hero-consumer'
}
}
},
]),
]
})
export class KafkaModule implements OnModuleInit {
constructor(#Inject('HERO_SERVICE') private readonly clientService: KafkaClient)
async onModuleInit() {
await this.clientService.connect();
}
}
The first thing I can't figure is what is the use of the first parameter of createMicroservice ? (I passed AppModule and KafkaModule and both worked correctly. knowing that kafkaModule is imported at appModule)
The other thing is that from what I understood, the microservice part and the configuration in the main.ts file is used to subscribe on the topics that is used in MessagePattern or EventPattern decorators, and the kafkaClient described in the kafkaModule is used to send messages to different topics.
the problem here is if what I said earlier is true, then why clientModule uses a default groupId if not specified to work as consumer. strange thing is I couldn't find a solution to get any message from any topic using clientModule.
what I'm doing right now is to use different group ids in each file so they wont have any conflicts.
The first parameter of createMicroservice, it will help to guide how the consumer will connect to Kafka when you want to consume a message from a specific topic.
Example: we want to get message from topic: test01
How we declare?
import {Controller} from '#nestjs/common'
import {MessagePattern, Payload} from '#nestjs/microservices'
#Controller('sync')
export class SyncController {
#MessagePattern('test01')
handleTopicTest01(#Payload() message: Sync): any {
// Handle your message here
}
}
The second block is used as producer that is not consumer. When application want to send message to a specific topic, the clientModel will support this.
#Get()
sayHello() {
return this.clientModule.send('say.hello', 'hello world')
}
Related
I have a problem with Nestjs, with starting a microservice that listens to a messagebroker, while simultaneously starting a websocket server with which we can communicate with front-end clients.
So I am aware of hybrid nest applications, where the websocket gateway can be injected into the microservice. However I get errors about Nest not being able to resolve the Gateway import.
I configured the microservice in a ResultsModule, the websocket in UsersModule.
The error I get is:
ERROR [ExceptionHandler] Nest can't resolve dependencies of the ResultsController (ResultsService, ?). Please make sure that the argument UsersGateway at index [1] is available in the ResultsModule context.
These are the relevant pieces of code:
main.js
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.RMQ,
options: {
urls: [
`amqp://${config.rabbitmq.username}:${config.rabbitmq.password}#${config.rabbitmq.host}:${config.rabbitmq.port}`,
],
queue: config.rabbitmq.channelListen,
noAck: false,
queueOptions: {
durable: false,
},
},
});
await app.startAllMicroservices();
await app.listen(82);
}
bootstrap();
app.module.ts
#Module({
imports: [UsersModule, ResultsModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
results.module.ts
#Module({
imports: [UsersModule],
controllers: [ResultsController],
providers: [ResultsService],
})
export class ResultsModule {}
users.module.ts
#Module({
providers: [UsersGateway, UsersService],
})
export class UsersModule {}
users.gateway.ts
import {
WebSocketGateway,
SubscribeMessage,
MessageBody,
OnGatewayInit,
OnGatewayConnection,
OnGatewayDisconnect,
WebSocketServer,
ConnectedSocket,
} from '#nestjs/websockets';
import { Socket, Server } from 'socket.io';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
#WebSocketGateway(82)
export class UsersGateway
implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
constructor(private readonly usersService: UsersService) {}
#WebSocketServer() server: Server;
afterInit(server: Server) {
console.log('init');
}
handleDisconnect(client: Socket) {
console.log(`Client disconnected: ${client.id}`);
}
handleConnection(client: Socket, ...args: any[]) {
console.log(client.id);
}
#SubscribeMessage('resource')
create(
#MessageBody() createUserDto: CreateUserDto,
#ConnectedSocket() client: Socket,
) {
console.log(client.id);
console.log(createUserDto.resourceId)
}
}
I tried to move around the imports, not wrapping the gateway in a module.
Tried moving the results module out of the app.module. Which removes the error, however then I do not understand where to let nestjs know to use the resultsmodule for the microservice.
------------------------ANSWER-------------------------------------
Alright, we fixed it with the help of #yomateo , we were missing the export of the websocket gateway in the module. Thanks for the help!
You're close (you have it backwards).
Have your UsersGateway or UsersService call the ResultsService.
In app.module.ts I have the following:
#Module({
imports: [
...,
GraphQLModule.forRoot<ApolloGatewayDriverConfig>({
server: {
context: getContext,
},
driver: ApolloGatewayDriver,
gateway: {
buildService: ({ name, url }) => {
return new RemoteGraphQLDataSource({
url,
willSendRequest({ request, context }: any) {
...
},
});
},
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'iam', url: API_URL_IAM },
],
})
},
}),
]
...
})
here getContext is just a regular function which is not part of nestjs context (doesn't have injection, module capability) like below:
export const getContext = async ({ req }) => {
return {}
}
Is there any way to use nestjs services instead of plain old functional approach to build the context for graphql gateway in nestjs?
Thanks in advance for any kind of help.
I believe you're looking to create a service that is #Injectable and you can use that injectable service via a provider. What a provider will do is satisfy any dependency injection necessary.
In your scenario, I would import other modules as necessary. For building context, I would create a config file to create from env variables. Then create a custom provider that reads from the env variables and provides that implementation of the class/service to the other classes as their dependency injection.
For example, if I have a graphQL module. I would import the independent module. Then, I would provide in the providers section, the handler/service classes and the dependencies as an #injectable. Once your service class is created based on your config (which your provider class would handle), you would attach that service class to your GraphQL class to maybe lets say direct the URL to your dev/prod envs.
Nestjs application does not display the error message during development only during production. I found no module where the apollo -server can be configured to cache: "bounded". The Nestjs documentation itself makes no mention of it anywhere.
The complete error message says:
Persisted queries are enabled and are using an unbounded cache. Your server is vulnerable to denial of service attacks via memory exhaustion. Set cache: "bounded" or persistedQueries: false in your ApolloServer constructor, or see https://go.apollo.dev/s/cache-backends for other alternatives.
Here are some dependencies I suspect could be related to it.
"#nestjs/apollo": "10.0.19",
"#nestjs/common": "9.0.5",
"#nestjs/core": "9.0.5",
"#nestjs/graphql": "10.0.20",
A similar issue was opened at github and sadly it was closed without any solution.
This is related to the Apollo module's configuration, by default the cache is "unbounded" and not safe for production (see : https://www.apollographql.com/docs/apollo-server/performance/cache-backends/#ensuring-a-bounded-cache)
You can easily follow their recommendation by adding the optional "cache" configuration inside your GraphQL module.
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
cache: 'bounded', // <--- This option
...
}
Or provide an external caching with KeyvAdapter see: https://github.com/apollographql/apollo-utils/tree/main/packages/keyvAdapter#keyvadapter-class
In the app.module.ts inside the import array,
GraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: ApolloDriver,
useClass: GqlConfigService,
}),
Then in the GqlConfigService I add cache: 'bounded' in the ApolloDriverConfig options.
import { ConfigService } from '#nestjs/config';
import { ApolloDriverConfig } from '#nestjs/apollo';
import { Injectable } from '#nestjs/common';
import { GqlOptionsFactory } from '#nestjs/graphql';
import { GraphqlConfig } from './config.interface';
#Injectable()
export class GqlConfigService implements GqlOptionsFactory {
constructor(private configService: ConfigService) {}
createGqlOptions(): ApolloDriverConfig {
const graphqlConfig = this.configService.get<GraphqlConfig>('graphql');
return {
// schema options
cache: 'bounded', // ! <== Added here
autoSchemaFile: graphqlConfig.schemaDestination || './src/schema.graphql',
sortSchema: graphqlConfig.sortSchema,
buildSchemaOptions: {
numberScalarMode: 'integer',
},
// subscription
installSubscriptionHandlers: true,
debug: graphqlConfig.debug,
playground: graphqlConfig.playgroundEnabled,
context: ({ req }) => ({ req }),
};
}
}
I am developing apis & microservices in nestJS,
this is my controller function
#Post()
#MessagePattern({ service: TRANSACTION_SERVICE, msg: 'create' })
create( #Body() createTransactionDto: TransactionDto_create ) : Promise<Transaction>{
return this.transactionsService.create(createTransactionDto)
}
when i call post api, dto validation works fine, but when i call this using microservice validation does not work and it passes to service without rejecting with error.
here is my DTO
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
export class TransactionDto_create{
#IsNotEmpty()
action: string;
// #IsString()
readonly rec_id : string;
#IsNotEmpty()
readonly data : Object;
extras : Object;
// readonly extras2 : Object;
}
when i call api without action parameter it shows error action required but when i call this from microservice using
const pattern = { service: TRANSACTION_SERVICE, msg: 'create' };
const data = {id: '5d1de5d787db5151903c80b9', extras:{'asdf':'dsf'}};
return this.client.send<number>(pattern, data)
it does not throw error and goes to service.
I have added globalpipe validation also.
app.useGlobalPipes(new ValidationPipe({
disableErrorMessages: false, // set true to hide detailed error message
whitelist: false, // set true to strip params which are not in DTO
transform: false // set true if you want DTO to convert params to DTO class by default its false
}));
how will it work for both api & microservice, because i need all at one place and with same functionality so that as per clients it can be called.
ValidationPipe throws HTTP BadRequestException, where as the proxy client expects RpcException.
#Catch(HttpException)
export class RpcValidationFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
return new RpcException(exception.getResponse())
}
}
#UseFilters(new RpcValidationFilter())
#MessagePattern('validate')
async validate(
#Payload(new ValidationPipe({ whitelist: true })) payload: SomeDTO,
) {
// payload validates to SomeDto
. . .
}
I'm going out on a limb and assuming in you main.ts you have the line app.useGlobalPipes(new ValidationPipe());. From the documentation
In the case of hybrid apps the useGlobalPipes() method doesn't set up pipes for gateways and micro services. For "standard" (non-hybrid) microservice apps, useGlobalPipes() does mount pipes globally.
You could instead bind the pipe globally from the AppModule, or you could use the #UsePipes() decorator on each route that will be needing validation via the ValidationPipe
More info on binding pipes here
As I understood, useGlobalPipes is working fine for api but not for microservice.
Reason behind this, nest microservice is a hybrid application and it has some restrictions. Please refer below para.
By default a hybrid application will not inherit global pipes, interceptors, guards and filters configured for the main (HTTP-based) application. To inherit these configuration properties from the main application, set the inheritAppConfig property in the second argument (an optional options object) of the connectMicroservice() call.
Please refer this Nest Official Document
So, you need to add inheritAppConfig option in connectMicroservice() method.
const microservice = app.connectMicroservice(
{
transport: Transport.TCP,
},
{ inheritAppConfig: true },
);
It worked for me!
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
I'm building an ASP .NET Core App with Angular-CLI using this tutorial.
App works great, I got pre-build event to ng build before starting the app in VS, it works OK. I also can successfully get to my API over localhost:port/api/[controller] (in this case it would be Contacts as it's supposed to be a contact book).
Now, I'm trying to have Angular get the data directly from API. For this reason, I created a IContact interface, a ContactService and I pass it onto ContactComponent which is supposed to display it.
I might be making a silly mistake here (my skills are very basic), but for some reason I don't even see the object .json coming in through the network logs when I run the app (before trying to pass it to the view I'm trying to ensure I'm getting the data from the API first).
I might be doing something wrong (so I encourage you to reply even if you think you might be saying silly-obvious stuff) but here's my questions:
Should my code below work (not attaching imports though, I think I got all of them but check me ;-))? I'm not talking super-efficient or stuff, just basic to get the job done.
What is the best way to see if service to getAPI is working? Would that be network logging in your browser if you just import the service into the component and try to call the get method? Or is there another way?
Is my logic and approach towards the general architecture of the app OK or am I getting something wrong? :-)
contact.service.ts
const API_URL = environment.apiUrl;
#Injectable()
export class ContactService {
constructor(private http: Http) { }
public getContacts(): Observable<IContact[]> {
return this.http.get(API_URL)
.map((response: Response) => <IContact[]>response.json())
.catch(this.handleError);
}
private handleError(error: Response) {
console.error(error);
return Observable.throw(error.json().error || 'Server error')
}
}
icontact.ts
export interface IContact {
id: number;
firstName: string,
lastName: string,
address: string,
telephone: string
}
contact.component.ts
#Component({
selector: 'app-contact',
templateUrl: './contact.component.html',
styleUrls: ['./contact.component.css'],
providers: [ContactService]
})
export class ContactComponent implements OnInit {
private _contactService: ContactService;
private contactlist: IContact[];
constructor() {
}
public ngOnInit() {
this._contactService.getContacts()
.subscribe((contacts) => { this.contactlist = contacts });
}
}
Any other code requirements or anything - let me know. All feedback is appreciated. Thanks!
I have slightly changed your code.
File icontact.ts. I have made IContact a simple type. You can make it an interface back to support your object shape.
export type IContact = string;
File contact.service.ts. We call the /api/values API that is created by default in a new ASP.NET Core Web API project if you follow the tutorial you mentioned.
import { Injectable } from "#angular/core";
import { Http } from "#angular/http";
import { Observable } from "rxjs/Observable";
import 'rxjs/add/operator/map'
import { IContact } from "./icontact";
const API_URL = '/api/values'; // environment.apiUrl;
#Injectable()
export class ContactService {
constructor(private http: Http) { }
public getContacts(): Observable<IContact[]> {
return this.http.get(API_URL)
.map(response => <IContact[]>response.json());
}
}
File contact.component.ts. The template simply displays the list.
import { Component, OnInit } from "#angular/core";
import { ContactService } from "./contact.service";
import { IContact } from "./icontact";
#Component({
selector: 'app-contact',
template: '<div *ngFor="let contact of contactList">{{contact}}</div>',
providers: [ContactService]
})
export class ContactComponent implements OnInit {
private contactList: IContact[];
constructor(private contactService: ContactService) { }
public ngOnInit() {
this.contactService.getContacts().subscribe(
(contacts) => { this.contactList = contacts; },
(error) => { console.log(error); }
);
}
}
File app.component.html. Display your component on the application's page.
<app-contact>Wait...</app-contact>
File app.module.ts. Import HttpModule. That fixes one of the error messages on your screenshot.
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { HttpModule } from '#angular/http';
import { AppComponent } from './app.component';
import { ContactComponent } from "./contact.component";
#NgModule({
declarations: [AppComponent, ContactComponent],
imports: [BrowserModule, HttpModule],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
In the Configure method in Startup.cs, make sure you put the app.Use(...) call before app.UseStaticFiles().
Since you serve your Angular app from wwwroot by a ASP.NET Core middleware, the host is the same for the Angular app and the Web API, so you don't need to configure CORS for that setup.
By the way, you may be interested in taking a look at the Angular CLI Templates on Visual Studio Marketplace. (Disclaimer: I'm the author.) The Item Template supports your project setup out-of-the-box.