9

I'm trying to use NestJS and the NATS microservice. There is good documentation for setting up a basic request-response.

What I did is the following:

Ran a local NATS server.

Set up my main.ts to connect to the server:

async function bootstrap() {
  const app = await NestFactory.createMicroservice(AppModule, {
    options: {
      url: "nats://localhost:4222",
    },
    transport: Transport.NATS,
  });
  app.listen(() => console.log("Microservice is listening"));
}
bootstrap();

Created a ClientProxyFactory to send back messages:

export const NatsClientProvider: Provider = {
  inject: [ConfigService],
  provide: NatsClientProviderId,
  useFactory: async (config: ConfigService) =>
    ClientProxyFactory.create({
      options: {
        servers: config.getNatsConfig().servers,
      },
      transport: Transport.NATS,
    }),
};

Set up a controller app.controller.ts to respond to a certain pattern:

@Controller()
export class AppController {
  constructor(
    private readonly appService: AppService,
    @Inject(NatsClientProviderId) private readonly natsClient: ClientProxy,
  ) {}

  @MessagePattern("hello")
  async getHello(data: string) {
    console.log("data: ", data);
    console.log("getHello!!");
    await this.natsClient.send("hello", this.appService.getHello());
    return this.appService.getHello();
  }

  async onModuleInit() {
    await this.natsClient.connect();
    console.log("Nats connected!");
  }

Set up a test file to try sending a request-response message:

import { connect } from "ts-nats";

async function start() {
  const nc = await connect({
    servers: ["nats://localhost:4222"],
  });

  const msg = await nc.request("hello", 5000, "me");
  console.log("msg: ", msg);
}

start();

When I run my Nest app, I can see the subscription created properly in the NATS server logs.

When I run the test.ts file, it times out with NatsError: Request timed out.. However, I can see my console logs (although the data is undefined even though I am specifying it in the published message.

Neither the return nor the client.send methods are working to receive messages back from the app.

Any help is appreciated!

EDIT: Still looking into and stuck on this issue. In the "Sending Messages" section of the Microservice docs, it says "The pattern has to be equal to this one defined in the @MessagePattern() decorator while payload is a message that we want to transmit to another microservice.". If I do that, the Nest app detects the message it sends and gets stuck in an infinite loop of sending a message and receiving the same message back and forth to itself forever.

Kim Kern
  • 54,283
  • 17
  • 197
  • 195
rhlsthrm
  • 769
  • 1
  • 12
  • 23

2 Answers2

5

To avoid the infinite loop in your controller, remove the natsClient.send statement. MessagePattern will automatically send a reply with the data you return from the function, in your case this.appService.getHello():

@MessagePattern("hello")
async getHello(data: string) {
  console.log("data: ", data);
  return "Hello World!";
}

Nest requires you to send a long an id attribute (any string is fine) for it to be able to reply to a message. Just include it in the data json:

// Nest expects the data to have the following structure
const reply = await nc.request("hello", 500, JSON.stringify({ data: "Hello", id: "myid" }));
console.log({ reply });

In your nest log, you'll see the following log entry:

data: Hello

In your test script, you'll see this:

{ reply:
   { subject: '_INBOX.GJGL6RJFYXKMCF8CWXO0HB.GJGL6RJFYXKMCF8CWXO0B5',
     sid: 1,
     reply: undefined,
     size: 50,
     data: '{"err":null,"response":"Hello World!","id":"myid"}' 
} }
Kim Kern
  • 54,283
  • 17
  • 197
  • 195
  • This does not use the Request-Response pattern: https://github.com/nats-io/nats.ts. I should just be able to use `let msg = await nc.request('greeter', 1000, 'me');` which takes care of the full subscription, publish, and response. You may have figured out my data issue though. – rhlsthrm Jun 14 '19 at 16:47
  • To clarify, if I send the request like this: `const msg = await nc.request(JSON.stringify({ cmd: "hello" }),5000,JSON.stringify({data: "me",}));` I can console log the correct data, but my Nest app is not sending the response back. – rhlsthrm Jun 14 '19 at 18:35
  • Also, I tried the approach you mentioned, I do get the response from the test script but that is from the same script receiving its own message, nothing is received from the Nest app properly. The Nest app needs to send a response to my NATS server that the client (the test script) picks up and logs. – rhlsthrm Jun 14 '19 at 18:39
  • Sorry, you are right, this was not correct. Please see my edit. The missing bit was the `id` field. – Kim Kern Jun 20 '19 at 16:35
  • Thanks for the help! Is there any way to also get the subject of the input message in Nest? For example if I want to subscribe to `user.get.>` so that I can receive messages to `user.get.MY_USER_ID` and be able to see that `MY_USER_ID` was the in the subject. – rhlsthrm Jun 21 '19 at 13:13
  • Not that I know of. :/ Does it even work with wildcards in nest? – Kim Kern Jun 21 '19 at 14:02
  • If it does not work with wildcards, that is a non-starter for me. I worked around this issue by implementing my own NATS pub/sub layer, but I would have liked to use the Nest framework. I will open an issue with Nest about this. Thank you for all the help. – rhlsthrm Jun 21 '19 at 14:07
  • I just tested and it does in fact work with wildcard subscriptions the as per the example I provided (`@MessagePattern('hello.>')` and request subject `hello.world`). Now if I can just get the subject to print out I will be set. – rhlsthrm Jun 21 '19 at 14:12
  • 1
    I don't think this part of the API, but you could open an issue (or even pull request) for it. Shouldn't be to hard I think. – Kim Kern Jun 24 '19 at 11:04
3

When using the ClientProxy, send and emit return Observables. You need to "activate" those for them to do anything. So you can either subscribe to them, or change it to a Promise.

since you are using await you probably want to do

await this.natsClient.send("hello", this.appService.getHello()).toPromise();
ss1080
  • 31
  • 1
  • 3
  • Hmm, thanks for the info. I tried this and I'm still getting the infinite loop where the service keeps responding to its own message (seems like this is due to the `@MessagePattern` message being the same as the subject that is being sent. Although I assume it would have to be like this in order for the request-response to work, I just don't understand how to make it not respond to itself, since both the ClientProxy and the Nest microservice are connected to the same NATS server. – rhlsthrm Jun 14 '19 at 08:55